diff options
Diffstat (limited to 'tikzit-1/src')
265 files changed, 37757 insertions, 0 deletions
diff --git a/tikzit-1/src/Makefile.am b/tikzit-1/src/Makefile.am new file mode 100644 index 0000000..5108e9c --- /dev/null +++ b/tikzit-1/src/Makefile.am @@ -0,0 +1,178 @@ +if WINDOWS +sharedir = ../ +else +sharedir = @datarootdir@/tikzit +endif + +AM_OBJCFLAGS = @FOUNDATION_OBJCFLAGS@ \ + @GTK_CFLAGS@ \ + -I common \ + -I gtk \ + -DTIKZITSHAREDIR=\"$(sharedir)\" \ + -std=c99 \ + -D_GNU_SOURCE +LIBS = @FOUNDATION_LIBS@ \ + @GTK_LIBS@ +AM_YFLAGS = -d +PARSERFILES = common/tikzlexer.m common/tikzlexer.h common/tikzparser.m common/tikzparser.h +ICONFILES = ../draw-ellipse.png \ + ../draw-path.png \ + ../select-rectangular.png \ + ../transform-crop-and-resize.png \ + ../transform-move.png +EDGEDECFILES = ../AH_*.png ../ED_*.png + +bin_PROGRAMS = tikzit +BUILT_SOURCES = $(PARSERFILES) +tikzit_SOURCES = gtk/Application.m \ + gtk/BoundingBoxTool.m \ + gtk/CairoRenderContext.m \ + gtk/ColorRGB+IntegerListStorage.m \ + gtk/ColorRGB+Gtk.m \ + gtk/Configuration.m \ + gtk/ContextWindow.m \ + gtk/CreateEdgeTool.m \ + gtk/CreateNodeTool.m \ + gtk/Edge+Render.m \ + gtk/EdgeStyle+Gtk.m \ + gtk/EdgeStyle+Storage.m \ + gtk/EdgeStyleEditor.m \ + gtk/EdgeStyleSelector.m \ + gtk/EdgeStylesModel.m \ + gtk/EdgeStylesPalette.m \ + gtk/FileChooserDialog.m \ + gtk/HandTool.m \ + gtk/GraphEditorPanel.m \ + gtk/GraphRenderer.m \ + gtk/Menu.m \ + gtk/Node+Render.m \ + gtk/NodeStyle+Gtk.m \ + gtk/NodeStyle+Storage.m \ + gtk/NodeStyleEditor.m \ + gtk/NodeStylesModel.m \ + gtk/NodeStyleSelector.m \ + gtk/NodeStylesPalette.m \ + gtk/NSError+Glib.m \ + gtk/NSFileManager+Glib.m \ + gtk/NSString+Glib.m \ + gtk/PropertiesPane.m \ + gtk/PropertyListEditor.m \ + gtk/RecentManager.m \ + gtk/SelectTool.m \ + gtk/SelectionPane.m \ + gtk/SettingsDialog.m \ + gtk/Shape+Render.m \ + gtk/StyleManager+Storage.m \ + gtk/TikzDocument.m \ + gtk/ToolBox.m \ + gtk/WidgetSurface.m \ + gtk/Window.m \ + gtk/cairo_helpers.m \ + gtk/clipboard.m \ + gtk/gtkhelpers.m \ + gtk/logo.m \ + gtk/mkdtemp.m \ + gtk/main.m \ + gtk/tzstockitems.m \ + gtk/tztoolpalette.m \ + common/CircleShape.m \ + common/ColorRGB.m \ + common/DiamondShape.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/NSString+Tikz.m \ + common/NSString+Util.m \ + common/PickSupport.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 += \ + common/Preambles.m \ + gtk/PreambleEditor.m \ + gtk/Preambles+Storage.m \ + gtk/PreviewRenderer.m \ + gtk/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.h: common/tikzlexer.lm + $(AM_V_GEN)$(LEX) -o common/tikzlexer.m $^ +# ordering hack for parallel builds +common/tikzlexer.h: common/tikzlexer.m + +common/tikzparser.m common/tikzparser.h: common/tikzparser.ym + $(AM_V_GEN)$(YACC) --defines=common/tikzparser.h --output=common/tikzparser.m $^ +# ordering hack for parallel builds +common/tikzparser.h: common/tikzparser.m + +gtk/icondata.m: $(ICONFILES) + $(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 \ + > $@ + +gtk/logodata.m: ../share/icons/hicolor/*/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 \ + logo32 ../share/icons/hicolor/32x32/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 \ + > $@ + +gtk/edgedecdata.m: $(EDGEDECFILES) + $(AM_V_GEN)gdk-pixbuf-csource --struct --static --raw --build-list \ + AH_none_pixdata ../AH_none.png \ + AH_latex_head_pixdata ../AH_latex_head.png \ + AH_latex_tail_pixdata ../AH_latex_tail.png \ + AH_plain_head_pixdata ../AH_plain_head.png \ + AH_plain_tail_pixdata ../AH_plain_tail.png \ + ED_none_pixdata ../ED_none.png \ + ED_arrow_pixdata ../ED_arrow.png \ + ED_tick_pixdata ../ED_tick.png \ + > $@ + +gtk/Menu.m: gtk/icondata.m +gtk/logo.m: gtk/logodata.m +gtk/EdgeStyleEditor.m: gtk/edgedecdata.m + +EXTRA_DIST = gtk/*.h common/*.h \ + $(PARSERFILES) common/tikzlexer.lm common/tikzparser.ym \ + $(ICONFILES) gtk/icondata.m \ + gtk/logodata.m \ + $(EDGEDECFILES) gtk/edgedecdata.m \ + tikzit.rc ../tikzit.ico +MAINTAINERCLEANFILES = $(PARSERFILES) gtk/icondata.m gtk/logodata.m gtk/edgedecdata.m diff --git a/tikzit-1/src/common/CircleShape.h b/tikzit-1/src/common/CircleShape.h new file mode 100644 index 0000000..8215b92 --- /dev/null +++ b/tikzit-1/src/common/CircleShape.h @@ -0,0 +1,33 @@ +// +// CircleShape.h +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> +#import "Shape.h" + +@interface CircleShape : Shape { +} + + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/CircleShape.m b/tikzit-1/src/common/CircleShape.m new file mode 100644 index 0000000..f2d1d52 --- /dev/null +++ b/tikzit-1/src/common/CircleShape.m @@ -0,0 +1,57 @@ +// +// CircleShape.m +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "CircleShape.h" +#import "Node.h" +#import "Edge.h" + +@implementation CircleShape + +- (id)init { + self = [super init]; + if (self) { + Node *n0,*n1,*n2,*n3; + + n0 = [Node nodeWithPoint:NSMakePoint( 0.0f, 0.2f)]; + n1 = [Node nodeWithPoint:NSMakePoint( 0.2f, 0.0f)]; + n2 = [Node nodeWithPoint:NSMakePoint( 0.0f, -0.2f)]; + n3 = [Node nodeWithPoint:NSMakePoint(-0.2f, 0.0f)]; + + Edge *e0,*e1,*e2,*e3; + + e0 = [Edge edgeWithSource:n0 andTarget:n1]; [e0 setBend:-45]; + e1 = [Edge edgeWithSource:n1 andTarget:n2]; [e1 setBend:-45]; + e2 = [Edge edgeWithSource:n2 andTarget:n3]; [e2 setBend:-45]; + e3 = [Edge edgeWithSource:n3 andTarget:n0]; [e3 setBend:-45]; + + paths = [[NSSet alloc] initWithObjects:[NSArray arrayWithObjects:e0,e1,e2,e3,nil],nil]; + + styleTikz = @"circle"; + } + return self; +} + + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/ColorRGB.h b/tikzit-1/src/common/ColorRGB.h new file mode 100644 index 0000000..607ba64 --- /dev/null +++ b/tikzit-1/src/common/ColorRGB.h @@ -0,0 +1,64 @@ +// +// ColorRGB.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> +#import "RColor.h" + +@interface ColorRGB : NSObject<NSCopying> { + unsigned short red, green, blue; +} + +@property (assign) unsigned short red; +@property (assign) unsigned short green; +@property (assign) unsigned short blue; + +@property (assign) float redFloat; +@property (assign) float greenFloat; +@property (assign) float blueFloat; + +@property (readonly) NSString *name; + +- (RColor)rColor; +- (RColor)rColorWithAlpha:(CGFloat)alpha; + +- (NSString*)hexName; +- (BOOL)isEqual:(id)col; +- (float)distanceFromColor:(ColorRGB*)col; +- (int)hash; + +- (id)initWithRed:(unsigned short)r green:(unsigned short)g blue:(unsigned short)b; +- (id)initWithFloatRed:(float)r green:(float)g blue:(float)b; +- (id)initWithRColor:(RColor)color; + +- (void)setToClosestHashed; + ++ (ColorRGB*)colorWithRed:(unsigned short)r green:(unsigned short)g blue:(unsigned short)b; ++ (ColorRGB*)colorWithFloatRed:(float)r green:(float)g blue:(float)b; ++ (ColorRGB*)colorWithRColor:(RColor)color; + ++ (void)makeColorHash; ++ (void)releaseColorHash; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/ColorRGB.m b/tikzit-1/src/common/ColorRGB.m new file mode 100644 index 0000000..840d716 --- /dev/null +++ b/tikzit-1/src/common/ColorRGB.m @@ -0,0 +1,353 @@ +// +// ColorRGB.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "ColorRGB.h" +#import "util.h" + +typedef struct { +#if __has_feature(objc_arc) + __unsafe_unretained NSString *name; +#else + NSString *name; +#endif + unsigned short r, g, b; +} ColorRGBEntry; + +static const ColorRGBEntry kColors[147] = { + { @"AliceBlue", 240, 248, 255 }, + { @"AntiqueWhite", 250, 235, 215 }, + { @"Aqua", 0, 255, 255 }, + { @"Aquamarine", 127, 255, 212 }, + { @"Azure", 240, 255, 255 }, + { @"Beige", 245, 245, 220 }, + { @"Bisque", 255, 228, 196 }, + { @"Black", 0, 0, 0 }, + { @"BlanchedAlmond", 255, 235, 205 }, + { @"Blue", 0, 0, 255 }, + { @"BlueViolet", 138, 43, 226 }, + { @"Brown", 165, 42, 42 }, + { @"BurlyWood", 222, 184, 135 }, + { @"CadetBlue", 95, 158, 160 }, + { @"Chartreuse", 127, 255, 0 }, + { @"Chocolate", 210, 105, 30 }, + { @"Coral", 255, 127, 80 }, + { @"CornflowerBlue", 100, 149, 237 }, + { @"Cornsilk", 255, 248, 220 }, + { @"Crimson", 220, 20, 60 }, + { @"Cyan", 0, 255, 255 }, + { @"DarkBlue", 0, 0, 139 }, + { @"DarkCyan", 0, 139, 139 }, + { @"DarkGoldenrod", 184, 134, 11 }, + { @"DarkGray", 169, 169, 169 }, + { @"DarkGreen", 0, 100, 0 }, + { @"DarkGrey", 169, 169, 169 }, + { @"DarkKhaki", 189, 183, 107 }, + { @"DarkMagenta", 139, 0, 139 }, + { @"DarkOliveGreen", 85, 107, 47 }, + { @"DarkOrange", 255, 140, 0 }, + { @"DarkOrchid", 153, 50, 204 }, + { @"DarkRed", 139, 0, 0 }, + { @"DarkSalmon", 233, 150, 122 }, + { @"DarkSeaGreen", 143, 188, 143 }, + { @"DarkSlateBlue", 72, 61, 139 }, + { @"DarkSlateGray", 47, 79, 79 }, + { @"DarkSlateGrey", 47, 79, 79 }, + { @"DarkTurquoise", 0, 206, 209 }, + { @"DarkViolet", 148, 0, 211 }, + { @"DeepPink", 255, 20, 147 }, + { @"DeepSkyBlue", 0, 191, 255 }, + { @"DimGray", 105, 105, 105 }, + { @"DimGrey", 105, 105, 105 }, + { @"DodgerBlue", 30, 144, 255 }, + { @"FireBrick", 178, 34, 34 }, + { @"FloralWhite", 255, 250, 240 }, + { @"ForestGreen", 34, 139, 34 }, + { @"Fuchsia", 255, 0, 255 }, + { @"Gainsboro", 220, 220, 220 }, + { @"GhostWhite", 248, 248, 255 }, + { @"Gold", 255, 215, 0 }, + { @"Goldenrod", 218, 165, 32 }, + { @"Gray", 128, 128, 128 }, + { @"Grey", 128, 128, 128 }, + { @"Green", 0, 128, 0 }, + { @"GreenYellow", 173, 255, 47 }, + { @"Honeydew", 240, 255, 240 }, + { @"HotPink", 255, 105, 180 }, + { @"IndianRed", 205, 92, 92 }, + { @"Indigo", 75, 0, 130 }, + { @"Ivory", 255, 255, 240 }, + { @"Khaki", 240, 230, 140 }, + { @"Lavender", 230, 230, 250 }, + { @"LavenderBlush", 255, 240, 245 }, + { @"LawnGreen", 124, 252, 0 }, + { @"LemonChiffon", 255, 250, 205 }, + { @"LightBlue", 173, 216, 230 }, + { @"LightCoral", 240, 128, 128 }, + { @"LightCyan", 224, 255, 255 }, + { @"LightGoldenrodYellow", 250, 250, 210 }, + { @"LightGray", 211, 211, 211 }, + { @"LightGreen", 144, 238, 144 }, + { @"LightGrey", 211, 211, 211 }, + { @"LightPink", 255, 182, 193 }, + { @"LightSalmon", 255, 160, 122 }, + { @"LightSeaGreen", 32, 178, 170 }, + { @"LightSkyBlue", 135, 206, 250 }, + { @"LightSlateGray", 119, 136, 153 }, + { @"LightSlateGrey", 119, 136, 153 }, + { @"LightSteelBlue", 176, 196, 222 }, + { @"LightYellow", 255, 255, 224 }, + { @"Lime", 0, 255, 0 }, + { @"LimeGreen", 50, 205, 50 }, + { @"Linen", 250, 240, 230 }, + { @"Magenta", 255, 0, 255 }, + { @"Maroon", 128, 0, 0 }, + { @"MediumAquamarine", 102, 205, 170 }, + { @"MediumBlue", 0, 0, 205 }, + { @"MediumOrchid", 186, 85, 211 }, + { @"MediumPurple", 147, 112, 219 }, + { @"MediumSeaGreen", 60, 179, 113 }, + { @"MediumSlateBlue", 123, 104, 238 }, + { @"MediumSpringGreen", 0, 250, 154 }, + { @"MediumTurquoise", 72, 209, 204 }, + { @"MediumVioletRed", 199, 21, 133 }, + { @"MidnightBlue", 25, 25, 112 }, + { @"MintCream", 245, 255, 250 }, + { @"MistyRose", 255, 228, 225 }, + { @"Moccasin", 255, 228, 181 }, + { @"NavajoWhite", 255, 222, 173 }, + { @"Navy", 0, 0, 128 }, + { @"OldLace", 253, 245, 230 }, + { @"Olive", 128, 128, 0 }, + { @"OliveDrab", 107, 142, 35 }, + { @"Orange", 255, 165, 0 }, + { @"OrangeRed", 255, 69, 0 }, + { @"Orchid", 218, 112, 214 }, + { @"PaleGoldenrod", 238, 232, 170 }, + { @"PaleGreen", 152, 251, 152 }, + { @"PaleTurquoise", 175, 238, 238 }, + { @"PaleVioletRed", 219, 112, 147 }, + { @"PapayaWhip", 255, 239, 213 }, + { @"PeachPuff", 255, 218, 185 }, + { @"Peru", 205, 133, 63 }, + { @"Pink", 255, 192, 203 }, + { @"Plum", 221, 160, 221 }, + { @"PowderBlue", 176, 224, 230 }, + { @"Purple", 128, 0, 128 }, + { @"Red", 255, 0, 0 }, + { @"RosyBrown", 188, 143, 143 }, + { @"RoyalBlue", 65, 105, 225 }, + { @"SaddleBrown", 139, 69, 19 }, + { @"Salmon", 250, 128, 114 }, + { @"SandyBrown", 244, 164, 96 }, + { @"SeaGreen", 46, 139, 87 }, + { @"Seashell", 255, 245, 238 }, + { @"Sienna", 160, 82, 45 }, + { @"Silver", 192, 192, 192 }, + { @"SkyBlue", 135, 206, 235 }, + { @"SlateBlue", 106, 90, 205 }, + { @"SlateGray", 112, 128, 144 }, + { @"SlateGrey", 112, 128, 144 }, + { @"Snow", 255, 250, 250 }, + { @"SpringGreen", 0, 255, 127 }, + { @"SteelBlue", 70, 130, 180 }, + { @"Tan", 210, 180, 140 }, + { @"Teal", 0, 128, 128 }, + { @"Thistle", 216, 191, 216 }, + { @"Tomato", 255, 99, 71 }, + { @"Turquoise", 64, 224, 208 }, + { @"Violet", 238, 130, 238 }, + { @"Wheat", 245, 222, 179 }, + { @"White", 255, 255, 255 }, + { @"WhiteSmoke", 245, 245, 245 }, + { @"Yellow", 255, 255, 0 }, + { @"YellowGreen", 154, 205, 50 } +}; + +static NSMapTable *colorHash = nil; + +@implementation ColorRGB + ++ (void)initialize { + [self setKeys:[NSArray arrayWithObject:@"red"] triggerChangeNotificationsForDependentKey:@"redFloat"]; + [self setKeys:[NSArray arrayWithObject:@"green"] triggerChangeNotificationsForDependentKey:@"greenFloat"]; + [self setKeys:[NSArray arrayWithObject:@"blue"] triggerChangeNotificationsForDependentKey:@"blueFloat"]; + [self setKeys:[NSArray arrayWithObjects:@"red", @"green", @"blue", nil] + triggerChangeNotificationsForDependentKey:@"name"]; +} + +@synthesize red, green, blue; + +- (float)redFloat { return ((float)red)/255.0f; } +- (void)setRedFloat:(float)r { [self setRed:round(r*255.0f)]; } +- (float)greenFloat { return ((float)green)/255.0f; } +- (void)setGreenFloat:(float)g { [self setGreen:round(g*255.0f)]; } +- (float)blueFloat { return ((float)blue)/255.0f; } +- (void)setBlueFloat:(float)b { [self setBlue:round(b*255.0f)]; } + +- (int)hash { + return (red<<4) + (green<<2) + blue; +} + +- (id)initWithRed:(unsigned short)r green:(unsigned short)g blue:(unsigned short)b { + self = [super init]; + if (self) { + red = r; + green = g; + blue = b; + } + return self; +} + +- (id)initWithFloatRed:(float)r green:(float)g blue:(float)b { + self = [super init]; + if (self) { + red = round(r*255.0f); + green = round(g*255.0f); + blue = round(b*255.0f); + } + return self; +} + +- (id) initWithRColor:(RColor)color { + return [self initWithFloatRed:color.red green:color.green blue:color.blue]; +} + +- (RColor) rColor { + return MakeSolidRColor ([self redFloat], [self greenFloat], [self blueFloat]); +} + +- (RColor) rColorWithAlpha:(CGFloat)alpha { + return MakeRColor ([self redFloat], [self greenFloat], [self blueFloat], alpha); +} + +- (NSString*)name { + if (colorHash == nil) + [ColorRGB makeColorHash]; + return [colorHash objectForKey:self]; +} + +- (NSString*)hexName { + return [NSString stringWithFormat:@"hexcolor0x%.2x%.2x%.2x", red, green, blue]; +} + +- (NSComparisonResult)compare:(ColorRGB*)col { + if (red > [col red]) return NSOrderedDescending; + else if (red < [col red]) return NSOrderedAscending; + else { + if (green > [col green]) return NSOrderedDescending; + else if (green < [col green]) return NSOrderedAscending; + else { + if (blue > [col blue]) return NSOrderedDescending; + else if (blue < [col blue]) return NSOrderedAscending; + else return NSOrderedSame; + } + } +} + +- (BOOL)isEqual:(id)col { + return [self compare:col] == NSOrderedSame; +} + +- (float)distanceFromColor:(ColorRGB *)col { + float dr = (float)(red - [col red]); + float dg = (float)(green - [col green]); + float db = (float)(blue - [col blue]); + return dr*dr + dg*dg + db*db; +} + +- (id)copyWithZone:(NSZone*)zone { + ColorRGB *col = [[ColorRGB allocWithZone:zone] initWithRed:red green:green blue:blue]; + return col; +} + ++ (ColorRGB*)colorWithRed:(unsigned short)r green:(unsigned short)g blue:(unsigned short)b { + ColorRGB *col = [[ColorRGB alloc] initWithRed:r green:g blue:b]; +#if __has_feature(objc_arc) + return col; +#else + return [col autorelease]; +#endif +} + + ++ (ColorRGB*)colorWithFloatRed:(float)r green:(float)g blue:(float)b { + ColorRGB *col = [[ColorRGB alloc] initWithFloatRed:r green:g blue:b]; +#if __has_feature(objc_arc) + return col; +#else + return [col autorelease]; +#endif +} + ++ (ColorRGB*) colorWithRColor:(RColor)color { +#if __has_feature(objc_arc) + return [[self alloc] initWithRColor:color]; +#else + return [[[self alloc] initWithRColor:color] autorelease]; +#endif +} + ++ (void)makeColorHash { + NSMapTable *h = [[NSMapTable alloc] init]; + int i; + for (i = 0; i < 147; ++i) { + ColorRGB *col = [[ColorRGB alloc] initWithRed:kColors[i].r + green:kColors[i].g + blue:kColors[i].b]; + [h setObject:kColors[i].name forKey:col]; + //NSLog(@"adding color %@ (%d)", kColors[i].name, [col hash]); +#if ! __has_feature(objc_arc) + [col release]; +#endif + } + colorHash = h; +} + ++ (void)releaseColorHash { +#if ! __has_feature(objc_arc) + [colorHash release]; +#endif +} + +- (void)setToClosestHashed { + if (colorHash == nil) + [ColorRGB makeColorHash]; + float bestDist = -1; + ColorRGB *bestColor = nil; + NSEnumerator *enumerator = [colorHash keyEnumerator]; + ColorRGB *tryColor; + while ((tryColor = [enumerator nextObject]) != nil) { + float dist = [self distanceFromColor:tryColor]; + if (bestDist<0 || dist<bestDist) { + bestDist = dist; + bestColor = tryColor; + } + } + [self setRed:[bestColor red]]; + [self setGreen:[bestColor green]]; + [self setBlue:[bestColor blue]]; +} + +@end + +// vi:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/DiamondShape.h b/tikzit-1/src/common/DiamondShape.h new file mode 100644 index 0000000..8f63386 --- /dev/null +++ b/tikzit-1/src/common/DiamondShape.h @@ -0,0 +1,34 @@ +// +// DiamondShape.h +// TikZiT +// +// Copyright 2012 Alex Merry +// All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> +#import "Shape.h" + +@interface DiamondShape : Shape { +} + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 + diff --git a/tikzit-1/src/common/DiamondShape.m b/tikzit-1/src/common/DiamondShape.m new file mode 100644 index 0000000..1a578b8 --- /dev/null +++ b/tikzit-1/src/common/DiamondShape.m @@ -0,0 +1,63 @@ +// +// DiamondShape.m +// TikZiT +// +// Copyright 2011 Aleks Kissinger +// Copyright 2012 Alex Merry +// All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "DiamondShape.h" + +#import "Node.h" +#import "Edge.h" + +@implementation DiamondShape + +- (id)init { + self = [super init]; + + if (!self) + return nil; + + Node *n0,*n1,*n2,*n3; + float sz = 0.25f; + + n0 = [Node nodeWithPoint:NSMakePoint(0, sz)]; + n1 = [Node nodeWithPoint:NSMakePoint(sz, 0)]; + n2 = [Node nodeWithPoint:NSMakePoint(0,-sz)]; + n3 = [Node nodeWithPoint:NSMakePoint(-sz,0)]; + + Edge *e0,*e1,*e2,*e3; + + e0 = [Edge edgeWithSource:n0 andTarget:n1]; + e1 = [Edge edgeWithSource:n1 andTarget:n2]; + e2 = [Edge edgeWithSource:n2 andTarget:n3]; + e3 = [Edge edgeWithSource:n3 andTarget:n0]; + + paths = [[NSSet alloc] initWithObjects:[NSArray arrayWithObjects:e0,e1,e2,e3,nil],nil]; + + styleTikz = @"shape=diamond"; + + return self; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/Edge.h b/tikzit-1/src/common/Edge.h new file mode 100644 index 0000000..accf38c --- /dev/null +++ b/tikzit-1/src/common/Edge.h @@ -0,0 +1,401 @@ +// +// Edge.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + + +// Edge : store the data associated with an edge. Also, lazily compute +// bezier curve control points based on bend and the coordinates of +// the endpoints. + +#import "Node.h" +#import "EdgeStyle.h" + +/*! + @typedef enum EdgeBendMode + @brief Indicates the type of edge bend. + @var EdgeBendModeBasic A basic, one-angle bend. Positive values will be interpreted + as bend left, negative as bend right. + @var EdgeBendModeInOut A two-angle bend mode, using inAngle and outAngle. + */ +typedef enum { + EdgeBendModeBasic, + EdgeBendModeInOut +} EdgeBendMode; + +/*! + @class Edge + @brief A graph edge, with associated bend and style data. + @details A graph edge, with associated bend and style data. This class + also contains methods for computing the bezier control points + and the midpoint of the curve. + */ +@interface Edge : NSObject<NSCopying> { + Node *source; + Node *target; + Node *edgeNode; + int bend; + int inAngle, outAngle; + EdgeBendMode bendMode; + float weight; + EdgeStyle *style; + GraphElementData *data; + NSString *sourceAnchor; + NSString *targetAnchor; + + // When set to YES, lazily create the edge node, and keep it around when set + // to NO (at least until saved/loaded). + BOOL hasEdgeNode; + BOOL dirty; + + // these are all cached values computed from the above + NSPoint src; + NSPoint targ; + NSPoint head; + NSPoint tail; + NSPoint cp1; + NSPoint cp2; + NSPoint mid; + NSPoint midTan; + NSPoint headTan; + NSPoint tailTan; +} + +/*! + @property data + @brief Associated edge data. + */ +@property (copy) GraphElementData *data; + +// KVC methods +- (void) insertObject:(GraphElementProperty*)gep + inDataAtIndex:(NSUInteger)index; +- (void) removeObjectFromDataAtIndex:(NSUInteger)index; +- (void) replaceObjectInDataAtIndex:(NSUInteger)index + withObject:(GraphElementProperty*)gep; + +/*! + @property style + @brief Edge style. + */ +@property (retain) EdgeStyle *style; + +/*! + @property source + @brief Source node. + */ +@property (retain) Node *source; + +/*! + @property target + @brief Target node. + */ +@property (retain) Node *target; + +/*! + @property edgeNode + @brief A node attached to this edge, as in a label or tick. + */ +@property (retain) Node *edgeNode; + +/*! + @property sourceAnchor + @brief The source node anchor point, as in north or center. + */ +@property (copy) NSString *sourceAnchor; + +/*! + @property targetAnchor + @brief The target node anchor point, as in north or center. + */ +@property (copy) NSString *targetAnchor; + +/*! + @property hasEdgeNode + @brief A read/write property. When set to true, a new edge node is actually constructed. +*/ +@property (assign) BOOL hasEdgeNode; + +/*! + @property bend + @brief The degrees by which the edge bends. + */ +@property (assign) int bend; + +/*! + @property weight + @brief How close the edge will pass to control points. + */ +@property (assign) float weight; + +/*! + @property inAngle + @brief The angle by which the edge enters its target. + */ +@property (assign) int inAngle; + +/*! + @property outAngle + @brief The angle by which the edge leaves its target. + */ +@property (assign) int outAngle; + +/*! + @property bendMode + @brief The mode of the edge bend. Either simple bend in in/out style. + */ +@property (assign) EdgeBendMode bendMode; + +/*! + @property head + @brief The starting point of the edge. + @detail This value is computed based on the source, target and + either bend or in/out angles. It is where the edge + makes contact with the source node. + */ +@property (readonly) NSPoint head; + +/*! + @property tail + @brief The ending point of the edge. + @detail This value is computed based on the source, target and + either bend or in/out angles. It is where the edge + makes contact with the target node. + */ +@property (readonly) NSPoint tail; + +/*! + @property cp1 + @brief The first control point of the edge. + @detail This value is computed based on the source, target and + either bend or in/out angles. + */ +@property (readonly) NSPoint cp1; + +/*! + @property cp2 + @brief The second control point of the edge. + @detail This value is computed based on the source, target and + either bend or in/out angles. + */ +@property (readonly) NSPoint cp2; + +/*! + @property mid + @brief The midpoint of the curve. Computed from the source, target, and control points. + */ +@property (readonly) NSPoint mid; + +/*! + @property mid_tan + @brief The second point of a line tangent to the midpoint. (The first is the midpoint itself.) + */ +@property (readonly) NSPoint midTan; + +/*! + @property left_normal + @brief The second point in a line perp. to the edge coming from mid-point. (left side) + */ +@property (readonly) NSPoint leftNormal; + +/*! + @property left_normal + @brief The second point in a line perp. to the edge coming from mid-point. (right side) + */ +@property (readonly) NSPoint rightNormal; + +@property (readonly) NSPoint headTan; + +/*! + @property leftHeadNormal + */ +@property (readonly) NSPoint leftHeadNormal; + +/*! + @property rightHeadNormal + */ +@property (readonly) NSPoint rightHeadNormal; + +@property (readonly) NSPoint tailTan; + +/*! + @property leftTailNormal + */ +@property (readonly) NSPoint leftTailNormal; + +/*! + @property rightTailNormal + */ +@property (readonly) NSPoint rightTailNormal; + +/*! + @property isSelfLoop + @brief Returns YES if this edge is a self loop. + */ +@property (readonly) BOOL isSelfLoop; + +/*! + @property isStraight + @brief Returns YES if this edge can be drawn as a straight line (as opposed to a bezier curve). + */ +@property (readonly) BOOL isStraight; + + +/*! + @brief Construct a blank edge. + @result An edge. + */ +- (id)init; + +/*! + @brief Construct an edge with the given source and target. + @param s the source node. + @param t the target node. + @result An edge. + */ +- (id)initWithSource:(Node*)s andTarget:(Node*)t; + +/*! + @brief Force the recalculation of the derived properties. + */ +- (void)recalculateProperties; + +/*! + @brief Recompute the control points and midpoint. + */ +- (void)updateControls; + +/*! + @brief Push edge properties back into its <tt>GraphElementData</tt>. + */ +- (void)updateData; + +/*! + @brief Set edge properties from fields in <tt>GraphElementData</tt>. + */ +- (void)setAttributesFromData; + +/*! + @brief Use data.style to find and attach the <tt>EdgeStyle</tt> object from the given array. + */ +- (BOOL)attachStyleFromTable:(NSArray*)styles; + +/*! + @brief Convert the bend angle to an inAngle and outAngle. + */ +- (void)convertBendToAngles; + +/*! + @brief Set the bend angle to the average of the in and out angles. + */ +- (void)convertAnglesToBend; + +/*! + @brief Update this edge to look just like the given edge. + @param e an edge to mimic. + */ +- (void)setPropertiesFromEdge:(Edge *)e; + +/*! + @brief Get a bounding rect for this edge. + @detail Note that this may not be a tight bound. + */ +- (NSRect)boundingRect; + +/*! + @brief Moves the first control point, updating edge properties as necessary + @detail This will move a control point and adjust the weight and + bend (or outAngle) to fit. + + A courseness can be specified for both the weight and the + bend, allowing them to be constrained to certain values. For + example, passing 10 as the bend courseness will force the bend + to be a multiple of 5. Passing 0 for either of these will + cause the relevant value to be unconstrained. + @param point the new position of the control point + @param wc force the weight to be a multiple of this value (unless 0) + @param bc force the bend (or outAngle) to be a multiple of this value (unless 0) + @param link when in EdgeBendModeInOut, change both the in and out angles at once + */ +- (void) moveCp1To:(NSPoint)point withWeightCourseness:(float)wc andBendCourseness:(int)bc forceLinkControlPoints:(BOOL)link; + +/*! + @brief Moves the first control point, updating edge properties as necessary + @detail This will move a control point and adjust the weight and + bend (or outAngle) to fit. + + The same as moveCp1To:point withWeightCourseness:0.0f andBendCourseness:0 forceLinkControlPoints:No + @param point the new position of the control point + @param wc force the weight to be a multiple of this value (unless 0) + @param bc force the bend (or outAngle) to be a multiple of this value (unless 0) + @param link when in EdgeBendModeInOut, change both the in and out angles at once + */ +- (void) moveCp1To:(NSPoint)point; + +/*! + @brief Moves the first control point, updating edge properties as necessary + @detail This will move a control point and adjust the weight and + bend (or inAngle) to fit. + + A courseness can be specified for both the weight and the + bend, allowing them to be constrained to certain values. For + example, passing 10 as the bend courseness will force the bend + to be a multiple of 5. Passing 0 for either of these will + cause the relevant value to be unconstrained. + @param point the new position of the control point + @param wc force the weight to be a multiple of this value (unless 0) + @param bc force the bend (or inAngle) to be a multiple of this value (unless 0) + @param link when in EdgeBendModeInOut, change both the in and out angles at once + */ +- (void) moveCp2To:(NSPoint)point withWeightCourseness:(float)wc andBendCourseness:(int)bc forceLinkControlPoints:(BOOL)link; + +/*! + @brief Moves the first control point, updating edge properties as necessary + @detail This will move a control point and adjust the weight and + bend (or inAngle) to fit. + + The same as moveCp2To:point withWeightCourseness:0.0f andBendCourseness:0 forceLinkControlPoints:No + @param point the new position of the control point + */ +- (void) moveCp2To:(NSPoint)point; + +/*! + @brief Reverse edge direction, updating bend/inAngle/outAngle/etc + */ +- (void)reverse; + +/*! + @brief Factory method to make a blank edge. + @result An edge. + */ ++ (Edge*)edge; + +/*! + @brief Factory method to make an edge with the given source and target. + @param s a source node. + @param t a target node. + @result An edge. + */ ++ (Edge*)edgeWithSource:(Node*)s andTarget:(Node*)t; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Edge.m b/tikzit-1/src/common/Edge.m new file mode 100644 index 0000000..0c88e9d --- /dev/null +++ b/tikzit-1/src/common/Edge.m @@ -0,0 +1,757 @@ +// +// Edge.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "Edge.h" +#import "Shape.h" +#import "util.h" + +@implementation Edge + +- (id)init { + self = [super init]; + if (self) { + data = [[GraphElementData alloc] init]; + bend = 0; + inAngle = 135; + outAngle = 45; + bendMode = EdgeBendModeBasic; + weight = 0.4f; + dirty = YES; + source = nil; + target = nil; + edgeNode = nil; + sourceAnchor = @""; + targetAnchor = @""; + } + return self; +} + +- (id)initWithSource:(Node*)s andTarget:(Node*)t { + self = [self init]; + if (self) { + [self setSource:s]; + [self setTarget:t]; + edgeNode = nil; + + dirty = YES; + } + return self; +} + +- (BOOL)attachStyleFromTable:(NSArray*)styles { + NSString *style_name = [data propertyForKey:@"style"]; + + [self setStyle:nil]; + if (style_name == nil) return YES; + + for (EdgeStyle *s in styles) { + if ([[s name] compare:style_name]==NSOrderedSame) { + [self setStyle:s]; + return YES; + } + } + + // if we didn't find a style, fill in a default one +#if __has_feature(objc_arc) + style = [EdgeStyle defaultEdgeStyleWithName:style_name]; +#else + style = [[EdgeStyle defaultEdgeStyleWithName:style_name] retain]; +#endif + return NO; +} + +- (NSPoint) _findContactPointOn:(Node*)node at:(float)angle { + NSPoint rayStart = [node point]; + Shape *shape = [node shape]; + if (shape == nil) { + return rayStart; + } + + Transformer *shapeTrans = [node shapeTransformer]; + // rounding errors are a pain + NSRect searchArea = NSInsetRect([node boundsUsingShapeTransform:shapeTrans],-0.01,-0.01); + if (!NSPointInRect(rayStart, searchArea)) { + return rayStart; + } + + NSPoint rayEnd = findExitPointOfRay (rayStart, angle, searchArea); + + for (NSArray *path in [shape paths]) { + for (Edge *curve in path) { + NSPoint intersect; + [curve updateControls]; + if (lineSegmentIntersectsBezier (rayStart, rayEnd, + [shapeTrans toScreen:curve->tail], + [shapeTrans toScreen:curve->cp1], + [shapeTrans toScreen:curve->cp2], + [shapeTrans toScreen:curve->head], + &intersect)) { + // we just keep shortening the line + rayStart = intersect; + } + } + } + + return rayStart; +} + +- (NSPoint) _findTanFor:(NSPoint)pt usingSpanFrom:(float)t1 to:(float)t2 { + float dx = bezierInterpolate(t2, tail.x, cp1.x, cp2.x, head.x) - + bezierInterpolate(t1, tail.x, cp1.x, cp2.x, head.x); + float dy = bezierInterpolate(t2, tail.y, cp1.y, cp2.y, head.y) - + bezierInterpolate(t1, tail.y, cp1.y, cp2.y, head.y); + + // normalise + float len = sqrt(dx*dx+dy*dy); + if (len != 0) { + dx = (dx/len) * 0.1f; + dy = (dy/len) * 0.1f; + } + + return NSMakePoint (pt.x + dx, pt.y + dy); +} + +- (void)recalculateProperties { + dirty = YES; +} + +- (void)updateControls { + // check for external modification to the node locations + if (src.x != [source point].x || src.y != [source point].y || + targ.x != [target point].x || targ.y != [target point].y) + { + dirty = YES; + } + + if (dirty) { + src = [source point]; + targ = [target point]; + + float dx = (targ.x - src.x); + float dy = (targ.y - src.y); + + float angleSrc = 0.0f; + float angleTarg = 0.0f; + + if (bendMode == EdgeBendModeBasic) { + float angle = good_atan(dx, dy); + float bnd = (float)bend * (M_PI / 180.0f); + angleSrc = angle - bnd; + angleTarg = M_PI + angle + bnd; + } else if (bendMode == EdgeBendModeInOut) { + angleSrc = (float)outAngle * (M_PI / 180.0f); + angleTarg = (float)inAngle * (M_PI / 180.0f); + } + + tail = [self _findContactPointOn:source at:angleSrc]; + head = [self _findContactPointOn:target at:angleTarg]; + + // give a default distance for self-loops + float cdist = (dx==0.0f && dy==0.0f) ? weight : sqrt(dx*dx + dy*dy) * weight; + + cp1 = NSMakePoint(src.x + (cdist * cos(angleSrc)), + src.y + (cdist * sin(angleSrc))); + + cp2 = NSMakePoint(targ.x + (cdist * cos(angleTarg)), + targ.y + (cdist * sin(angleTarg))); + + mid = bezierInterpolateFull (0.5f, tail, cp1, cp2, head); + midTan = [self _findTanFor:mid usingSpanFrom:0.4f to:0.6f]; + + tailTan = [self _findTanFor:tail usingSpanFrom:0.0f to:0.1f]; + headTan = [self _findTanFor:head usingSpanFrom:1.0f to:0.9f]; + } + dirty = NO; +} + +- (void)convertBendToAngles { + float dx = (targ.x - src.x); + float dy = (targ.y - src.y); + float angle = good_atan(dx, dy); + float bnd = (float)bend * (M_PI / 180.0f); + + [self setOutAngle:round((angle - bnd) * (180.0f/M_PI))]; + [self setInAngle:round((M_PI + angle + bnd) * (180.0f/M_PI))]; + dirty = YES; +} + +- (void)convertAnglesToBend { + float dx = (targ.x - src.x); + float dy = (targ.y - src.y); + int angle = round((180.0f/M_PI) * good_atan(dx, dy)); + + // compute bend1 and bend2 to match inAngle and outAngle, resp. + int bend1, bend2; + + bend1 = outAngle - angle; + bend2 = angle - inAngle; + + [self setBend:(bend1 + bend2) / 2]; +} + +- (BOOL)isSelfLoop { + return (source == target); +} + +- (BOOL)isStraight { + return (bendMode == EdgeBendModeBasic && bend == 0); +} + +- (NSPoint)mid { + [self updateControls]; + return mid; +} + +- (NSPoint)midTan { + [self updateControls]; + return midTan; +} + +- (NSPoint)leftNormal { + [self updateControls]; + return NSMakePoint(mid.x + (mid.y - midTan.y), mid.y - (mid.x - midTan.x)); +} + +- (NSPoint)rightNormal { + [self updateControls]; + return NSMakePoint(mid.x - (mid.y - midTan.y), mid.y + (mid.x - midTan.x)); +} + +- (NSPoint)headTan { + [self updateControls]; + return headTan; +} + +- (NSPoint)leftHeadNormal { + [self updateControls]; + return NSMakePoint(headTan.x + (head.y - headTan.y), headTan.y - (head.x - headTan.x)); +} + +- (NSPoint)rightHeadNormal { + [self updateControls]; + return NSMakePoint(headTan.x - (head.y - headTan.y), headTan.y + (head.x - headTan.x)); +} + +- (NSPoint)tailTan { + [self updateControls]; + return tailTan; +} + +- (NSPoint)leftTailNormal { + [self updateControls]; + return NSMakePoint(tailTan.x + (tail.y - tailTan.y), tailTan.y - (tail.x - tailTan.x)); +} + +- (NSPoint)rightTailNormal { + [self updateControls]; + return NSMakePoint(tailTan.x - (tail.y - tailTan.y), tailTan.y + (tail.x - tailTan.x)); +} + +- (NSPoint) head { + [self updateControls]; + return head; +} + +- (NSPoint) tail { + [self updateControls]; + return tail; +} + +- (NSPoint)cp1 { + [self updateControls]; + return cp1; +} + +- (NSPoint)cp2 { + [self updateControls]; + return cp2; +} + +- (int)inAngle {return inAngle;} +- (void)setInAngle:(int)a { + inAngle = normaliseAngleDeg (a); + dirty = YES; +} + +- (int)outAngle {return outAngle;} +- (void)setOutAngle:(int)a { + outAngle = normaliseAngleDeg (a); + dirty = YES; +} + +- (EdgeBendMode)bendMode {return bendMode;} +- (void)setBendMode:(EdgeBendMode)mode { + bendMode = mode; + dirty = YES; +} + +- (int)bend {return bend;} +- (void)setBend:(int)b { + bend = normaliseAngleDeg (b); + dirty = YES; +} + +- (float)weight {return weight;} +- (void)setWeight:(float)w { +// if (source == target) weight = 1.0f; +// else weight = w; + weight = w; + dirty = YES; +} + +- (EdgeStyle*)style {return style;} +- (void)setStyle:(EdgeStyle*)s { + if (style != s) { +#if __has_feature(objc_arc) + style = s; +#else + [style release]; + style = [s retain]; +#endif + } +} + +- (Node*)source {return source;} +- (void)setSource:(Node *)s { + if (source != s) { + [source removeObserver:self + forKeyPath:@"style"]; + + if ([s style] == nil) { + [self setSourceAnchor:@"center"]; + } else if ([sourceAnchor isEqual:@"center"]) { + [self setSourceAnchor:@""]; + } + +#if __has_feature(objc_arc) + source = s; +#else + [source release]; + source = [s retain]; +#endif + + if (source==target) { + bendMode = EdgeBendModeInOut; + weight = 1.0f; + } + + [source addObserver:self + forKeyPath:@"style" + options:NSKeyValueObservingOptionNew + | NSKeyValueObservingOptionOld + context:NULL]; + + dirty = YES; + } +} + +- (Node*)target {return target;} +- (void)setTarget:(Node *)t { + if (target != t) { + [target removeObserver:self + forKeyPath:@"style"]; + + if ([t style] == nil) { + [self setTargetAnchor:@"center"]; + } else if ([targetAnchor isEqual:@"center"]) { + [self setTargetAnchor:@""]; + } + +#if __has_feature(objc_arc) + target = t; +#else + [target release]; + target = [t retain]; +#endif + + if (source==target) { + bendMode = EdgeBendModeInOut; + weight = 1.0f; + } + + [target addObserver:self + forKeyPath:@"style" + options:NSKeyValueObservingOptionNew + | NSKeyValueObservingOptionOld + context:NULL]; + + dirty = YES; + } +} + +- (void)observeValueForKeyPath:(NSString *)keyPath + ofObject:(id)object + change:(NSDictionary *)change + context:(void *)context + +{ + if ([keyPath isEqual:@"style"]) { + id oldStyle = [change objectForKey:NSKeyValueChangeOldKey]; + id newStyle = [change objectForKey:NSKeyValueChangeNewKey]; + id none = [NSNull null]; + if (object == source) { + if (oldStyle != none && newStyle == none) { + [self setSourceAnchor:@"center"]; + } else if (oldStyle == none && newStyle != none && + [sourceAnchor isEqual:@"center"]) { + [self setSourceAnchor:@""]; + } + } + if (object == target) { + if (oldStyle != none && newStyle == none) { + [self setTargetAnchor:@"center"]; + } else if (oldStyle == none && newStyle != none && + [targetAnchor isEqual:@"center"]) { + [self setTargetAnchor:@""]; + } + } + } + dirty = YES; +} + + +// edgeNode and hasEdgeNode use a bit of key-value observing to help the mac GUI keep up + +- (Node*)edgeNode {return edgeNode;} +- (void)setEdgeNode:(Node *)n { + [self willChangeValueForKey:@"edgeNode"]; + [self willChangeValueForKey:@"hasEdgeNode"]; + if (edgeNode != n) { + hasEdgeNode = (n != nil); +#if __has_feature(objc_arc) + edgeNode = n; +#else + [edgeNode release]; + edgeNode = [n retain]; +#endif + // don't set dirty bit, because control points don't need update + } + [self didChangeValueForKey:@"edgeNode"]; + [self didChangeValueForKey:@"hasEdgeNode"]; +} + +- (BOOL)hasEdgeNode { return hasEdgeNode; } +- (void)setHasEdgeNode:(BOOL)b { + [self willChangeValueForKey:@"edgeNode"]; + [self willChangeValueForKey:@"hasEdgeNode"]; + hasEdgeNode = b; + if (hasEdgeNode && edgeNode == nil) { + edgeNode = [[Node alloc] init]; + } + [self didChangeValueForKey:@"edgeNode"]; + [self didChangeValueForKey:@"hasEdgeNode"]; +} + +- (NSString*) sourceAnchor { return sourceAnchor; } +- (void)setSourceAnchor:(NSString *)_sourceAnchor{ + NSString *oldSourceAnchor = sourceAnchor; + if(_sourceAnchor != nil){ + sourceAnchor = [_sourceAnchor copy]; + }else{ + sourceAnchor = @""; + } +#if ! __has_feature(objc_arc) + [oldSourceAnchor release]; +#endif +} + +- (NSString*) targetAnchor { return targetAnchor; } +- (void)setTargetAnchor:(NSString *)_targetAnchor{ + NSString *oldTargetAnchor = targetAnchor; + if(_targetAnchor != nil){ + targetAnchor = [_targetAnchor copy]; + }else{ + targetAnchor = @""; + } +#if ! __has_feature(objc_arc) + [oldTargetAnchor release]; +#endif +} + +@synthesize data; +- (void) insertObject:(GraphElementProperty*)gep + inDataAtIndex:(NSUInteger)index { + [data insertObject:gep atIndex:index]; +} +- (void) removeObjectFromDataAtIndex:(NSUInteger)index { + [data removeObjectAtIndex:index]; +} +- (void) replaceObjectInDataAtIndex:(NSUInteger)index + withObject:(GraphElementProperty*)gep { + [data replaceObjectAtIndex:index withObject:gep]; +} + +- (void)updateData { + // unset everything to avoid redundant defs + [data unsetAtom:@"loop"]; + [data unsetProperty:@"in"]; + [data unsetProperty:@"out"]; + [data unsetAtom:@"bend left"]; + [data unsetAtom:@"bend right"]; + [data unsetProperty:@"bend left"]; + [data unsetProperty:@"bend right"]; + [data unsetProperty:@"looseness"]; + + if (style == nil) { + [data unsetProperty:@"style"]; + } else { + [data setProperty:[style name] forKey:@"style"]; + } + + if (bendMode == EdgeBendModeBasic && bend != 0) { + NSString *bendkey = @"bend right"; + int b = [self bend]; + if (b < 0) { + bendkey = @"bend left"; + b = -b; + } + + if (b == 30) { + [data setAtom:bendkey]; + } else { + [data setProperty:[NSString stringWithFormat:@"%d",b] forKey:bendkey]; + } + + } else if (bendMode == EdgeBendModeInOut) { + [data setProperty:[NSString stringWithFormat:@"%d",inAngle] + forKey:@"in"]; + [data setProperty:[NSString stringWithFormat:@"%d",outAngle] + forKey:@"out"]; + } + + // loop needs to come after in/out + if (source == target) [data setAtom:@"loop"]; + + if (![self isSelfLoop] && ![self isStraight]) + { + [data setProperty:[NSString stringWithFormat:@"%.2f",weight*2.5f] + forKey:@"looseness"]; + } +} + +- (void)setAttributesFromData { + bendMode = EdgeBendModeBasic; + + if ([data isAtomSet:@"bend left"]) { + [self setBend:-30]; + } else if ([data isAtomSet:@"bend right"]) { + [self setBend:30]; + } else if ([data propertyForKey:@"bend left"] != nil) { + NSString *bnd = [data propertyForKey:@"bend left"]; + [self setBend:-[bnd intValue]]; + } else if ([data propertyForKey:@"bend right"] != nil) { + NSString *bnd = [data propertyForKey:@"bend right"]; + [self setBend:[bnd intValue]]; + } else { + [self setBend:0]; + + if ([data propertyForKey:@"in"] != nil && [data propertyForKey:@"out"] != nil) { + bendMode = EdgeBendModeInOut; + [self setInAngle:[[data propertyForKey:@"in"] intValue]]; + [self setOutAngle:[[data propertyForKey:@"out"] intValue]]; + } + } + + if ([data propertyForKey:@"looseness"] != nil) { + weight = [[data propertyForKey:@"looseness"] floatValue] / 2.5f; + } else { + weight = ([self isSelfLoop]) ? 1.0f : 0.4f; + } +} + +- (void)setPropertiesFromEdge:(Edge*)e { + Node *en = [[e edgeNode] copy]; + [self setEdgeNode:en]; +#if ! __has_feature(objc_arc) + [en release]; +#endif + + GraphElementData *d = [[e data] copy]; + [self setData:d]; +#if ! __has_feature(objc_arc) + [d release]; +#endif + + [self setStyle:[e style]]; + [self setBend:[e bend]]; + [self setInAngle:[e inAngle]]; + [self setOutAngle:[e outAngle]]; + [self setBendMode:[e bendMode]]; + [self setWeight:[e weight]]; + + dirty = YES; // cached data will be recomputed lazily, rather than copied +} + +- (NSRect)boundingRect { + [self updateControls]; + NSRect bound = NSRectAround4Points(head, tail, cp1, cp2); + if ([self style] != nil) { + switch ([[self style] decorationStyle]) { + case ED_Arrow: + bound = NSRectWithPoint(bound, [self midTan]); + case ED_Tick: + bound = NSRectWithPoint(bound, [self leftNormal]); + bound = NSRectWithPoint(bound, [self rightNormal]); + case ED_None: + break; + } + if ([[self style] headStyle] != AH_None) { + bound = NSRectWithPoint(bound, [self leftHeadNormal]); + bound = NSRectWithPoint(bound, [self rightHeadNormal]); + } + if ([[self style] tailStyle] != AH_None) { + bound = NSRectWithPoint(bound, [self leftTailNormal]); + bound = NSRectWithPoint(bound, [self rightTailNormal]); + } + } + return bound; +} + +- (void) adjustWeight:(float)handle_dist withCourseness:(float)wcourseness { + float base_dist = NSDistanceBetweenPoints (src, targ); + if (base_dist == 0.0f) { + base_dist = 1.0f; + } + + [self setWeight:roundToNearest(wcourseness, handle_dist / base_dist)]; +} + +- (float) angleOf:(NSPoint)point relativeTo:(NSPoint)base { + float dx = point.x - base.x; + float dy = point.y - base.y; + return radiansToDegrees (good_atan(dx, dy)); +} + +- (void) moveCp1To:(NSPoint)point withWeightCourseness:(float)wc andBendCourseness:(int)bc forceLinkControlPoints:(BOOL)link { + [self updateControls]; + [self adjustWeight:NSDistanceBetweenPoints (point, src) withCourseness:wc]; + + float control_angle = [self angleOf:point relativeTo:src]; + if (bendMode == EdgeBendModeBasic) { + float base_angle = [self angleOf:targ relativeTo:src]; + int b = (int)roundToNearest (bc, base_angle - control_angle); + [self setBend:b]; + } else { + int angle = (int)roundToNearest (bc, control_angle); + if (link) { + [self setInAngle:(inAngle + angle - outAngle)]; + } + [self setOutAngle:angle]; + } +} + +- (void) moveCp1To:(NSPoint)point { + [self moveCp1To:point withWeightCourseness:0.0f andBendCourseness:0 forceLinkControlPoints:NO]; +} + +- (void) moveCp2To:(NSPoint)point withWeightCourseness:(float)wc andBendCourseness:(int)bc forceLinkControlPoints:(BOOL)link { + [self updateControls]; + + if (![self isSelfLoop]) { + [self adjustWeight:NSDistanceBetweenPoints (point, targ) withCourseness:wc]; + } + + float control_angle = [self angleOf:point relativeTo:targ]; + if (bendMode == EdgeBendModeBasic) { + float base_angle = [self angleOf:src relativeTo:targ]; + int b = (int)roundToNearest (bc, control_angle - base_angle); + [self setBend:b]; + } else { + int angle = (int)roundToNearest (bc, control_angle); + if (link) { + [self setOutAngle:(outAngle + angle - inAngle)]; + } + [self setInAngle: angle]; + } +} + +- (void) moveCp2To:(NSPoint)point { + [self moveCp2To:point withWeightCourseness:0.0f andBendCourseness:0 forceLinkControlPoints:NO]; +} + +- (void)reverse { + Node *n; + float f; + NSString *a; + + n = source; + source = target; + target = n; + + f = inAngle; + inAngle = outAngle; + outAngle = f; + + a = sourceAnchor; + sourceAnchor = targetAnchor; + targetAnchor = a; + + [self setBend:-bend]; + + dirty = YES; +} + +- (void)dealloc { + [source removeObserver:self + forKeyPath:@"style"]; + [target removeObserver:self + forKeyPath:@"style"]; +#if ! __has_feature(objc_arc) + [source release]; + [target release]; + [data release]; + [sourceAnchor release]; + [targetAnchor release]; + [super dealloc]; +#endif +} + +- (id)copyWithZone:(NSZone*)zone { + Edge *cp = [[Edge allocWithZone:zone] init]; + [cp setSource:[self source]]; + [cp setTarget:[self target]]; + [cp setSourceAnchor:[self sourceAnchor]]; + [cp setTargetAnchor:[self targetAnchor]]; + [cp setPropertiesFromEdge:self]; + return cp; +} + ++ (Edge*)edge { +#if __has_feature(objc_arc) + return [[Edge alloc] init]; +#else + return [[[Edge alloc] init] autorelease]; +#endif +} + ++ (Edge*)edgeWithSource:(Node*)s andTarget:(Node*)t { +#if __has_feature(objc_arc) + return [[Edge alloc] initWithSource:s andTarget:t]; +#else + return [[[Edge alloc] initWithSource:s andTarget:t] autorelease]; +#endif +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/EdgeStyle.h b/tikzit-1/src/common/EdgeStyle.h new file mode 100644 index 0000000..a51f129 --- /dev/null +++ b/tikzit-1/src/common/EdgeStyle.h @@ -0,0 +1,71 @@ +// +// EdgeStyle.h +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> +#import "PropertyHolder.h" +#import "ColorRGB.h" + +typedef enum { + AH_None = 0, + AH_Plain = 1, + AH_Latex = 2 +} ArrowHeadStyle; + +typedef enum { + ED_None = 0, + ED_Arrow = 1, + ED_Tick = 2 +} EdgeDectorationStyle; + +@interface EdgeStyle : PropertyHolder <NSCopying> { + ArrowHeadStyle headStyle, tailStyle; + EdgeDectorationStyle decorationStyle; + float thickness; + ColorRGB *colorRGB; + NSString *name; + NSString *category; +} + +/*! + @property colorRGB + @brief The color to render the line in + */ +@property (copy) ColorRGB *colorRGB; + +@property (copy) NSString *name; +@property (copy) NSString *category; +@property (assign) ArrowHeadStyle headStyle; +@property (assign) ArrowHeadStyle tailStyle; +@property (assign) EdgeDectorationStyle decorationStyle; +@property (assign) float thickness; + +@property (readonly) NSString *tikz; + +- (id)init; +- (id)initWithName:(NSString*)nm; ++ (EdgeStyle*)defaultEdgeStyleWithName:(NSString*)nm; +- (void) updateFromStyle:(EdgeStyle*)style; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/EdgeStyle.m b/tikzit-1/src/common/EdgeStyle.m new file mode 100644 index 0000000..c61e94a --- /dev/null +++ b/tikzit-1/src/common/EdgeStyle.m @@ -0,0 +1,222 @@ +// +// EdgeStyle.m +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "EdgeStyle.h" + +@implementation EdgeStyle + ++ (void)initialize { + [self setKeys:[NSArray arrayWithObjects: + @"tailStyle", + @"headStyle", + @"decorationStyle", + @"thickness", + @"colorRGB.red", + @"colorRGB.blue", + @"colorRGB.green", + @"name", + nil] + triggerChangeNotificationsForDependentKey:@"tikz"]; + [self setKeys:[NSArray arrayWithObjects: + @"colorRGB.name", + nil] + triggerChangeNotificationsForDependentKey:@"colorIsKnown"]; +} + +- (id)initWithName:(NSString*)nm { + self = [super initWithNotificationName:@"EdgeStylePropertyChanged"]; + + if (self != nil) { + headStyle = AH_None; + tailStyle = AH_None; + decorationStyle = ED_None; + colorRGB = [[ColorRGB alloc] initWithRed:0 green:0 blue:0]; + name = nm; + category = nil; + thickness = 1.0f; + } + + return self; +} + +- (id)init { + self = [self initWithName:@"new"]; + return self; +} + +- (id)copyWithZone:(NSZone*)zone { + EdgeStyle *style = [[EdgeStyle allocWithZone:zone] init]; + [style setName:[self name]]; + [style setCategory:[self category]]; + [style setHeadStyle:[self headStyle]]; + [style setTailStyle:[self tailStyle]]; + [style setDecorationStyle:[self decorationStyle]]; + [style setThickness:[self thickness]]; + [style setColorRGB:[self colorRGB]]; + return style; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [name release]; + [category release]; + [colorRGB release]; + [super dealloc]; +#endif +} + +- (NSString*) description { + return [NSString stringWithFormat:@"Edge style \"%@\"", name]; +} + +- (void) updateFromStyle:(EdgeStyle*)style { + [self setName:[style name]]; + [self setCategory:[style category]]; + [self setHeadStyle:[style headStyle]]; + [self setTailStyle:[style tailStyle]]; + [self setDecorationStyle:[style decorationStyle]]; + [self setThickness:[style thickness]]; + [self setColorRGB:[style colorRGB]]; +} + ++ (EdgeStyle*)defaultEdgeStyleWithName:(NSString*)nm { +#if __has_feature(objc_arc) + return [[EdgeStyle alloc] initWithName:nm]; +#else + return [[[EdgeStyle alloc] initWithName:nm] autorelease]; +#endif +} + +- (NSString*)name { return name; } +- (void)setName:(NSString *)s { + if (name != s) { + NSString *oldValue = name; + name = [s copy]; + [self postPropertyChanged:@"name" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (ArrowHeadStyle)headStyle { return headStyle; } +- (void)setHeadStyle:(ArrowHeadStyle)s { + ArrowHeadStyle oldValue = headStyle; + headStyle = s; + [self postPropertyChanged:@"headStyle" oldValue:[NSNumber numberWithInt:oldValue]]; +} + +- (ArrowHeadStyle)tailStyle { return tailStyle; } +- (void)setTailStyle:(ArrowHeadStyle)s { + ArrowHeadStyle oldValue = tailStyle; + tailStyle = s; + [self postPropertyChanged:@"tailStyle" oldValue:[NSNumber numberWithInt:oldValue]]; +} + +- (EdgeDectorationStyle)decorationStyle { return decorationStyle; } +- (void)setDecorationStyle:(EdgeDectorationStyle)s { + EdgeDectorationStyle oldValue = decorationStyle; + decorationStyle = s; + [self postPropertyChanged:@"decorationStyle" oldValue:[NSNumber numberWithInt:oldValue]]; +} +- (float)thickness { return thickness; } +- (void)setThickness:(float)s { + float oldValue = thickness; + thickness = s; + [self postPropertyChanged:@"thickness" oldValue:[NSNumber numberWithFloat:oldValue]]; +} + +- (NSString*)category { + return category; +} + +- (void)setCategory:(NSString *)s { + if (category != s) { + NSString *oldValue = category; + category = [s copy]; + [self postPropertyChanged:@"category" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (ColorRGB*)colorRGB { + return colorRGB; +} + +- (void)setColorRGB:(ColorRGB*)c { + if (colorRGB != c) { + ColorRGB *oldValue = colorRGB; + colorRGB = [c copy]; + [self postPropertyChanged:@"colorRGB" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (NSString*)tikz { + NSMutableString *buf = [NSMutableString stringWithFormat:@"\\tikzstyle{%@}=[", name]; + + NSString *colorName = [colorRGB name]; + if (colorName == nil) + colorName = [colorRGB hexName]; + + if (tailStyle == AH_Plain) + [buf appendString:@"<"]; + else if (tailStyle == AH_Latex) + [buf appendString:@"latex"]; + + [buf appendString:@"-"]; + + if (headStyle == AH_Plain) + [buf appendString:@">"]; + else if (headStyle == AH_Latex) + [buf appendString:@"latex"]; + + if(colorName != nil){ + [buf appendString:@",draw="]; + [buf appendString:colorName]; + } + + if (decorationStyle != ED_None) { + [buf appendString:@",postaction={decorate},decoration={markings,mark="]; + if (decorationStyle == ED_Arrow) + [buf appendString:@"at position .5 with {\\arrow{>}}"]; + else if (decorationStyle == ED_Tick) + [buf appendString:@"at position .5 with {\\draw (0,-0.1) -- (0,0.1);}"]; + [buf appendString:@"}"]; + } + + if (thickness != 1.0f) { + [buf appendFormat:@",line width=%.3f", thickness]; + } + + [buf appendString:@"]"]; + return buf; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/Graph.h b/tikzit-1/src/common/Graph.h new file mode 100644 index 0000000..1f98858 --- /dev/null +++ b/tikzit-1/src/common/Graph.h @@ -0,0 +1,401 @@ +// +// Graph.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + + +/*! + + @mainpage TikZiT + TikZiT is a GUI application for drawing, editing, and parsing TikZ + diagrams. Common code is in src/common, and plaform-specific code is + in src/{osx,linux}. + + */ + + +#import "Node.h" +#import "Edge.h" +#import "GraphChange.h" +#import "Transformer.h" + +/*! + @class Graph + @brief Store a graph, output to TikZ. + @details All of the methods that change a graph return an object of type GraphChange. + Graph changes can be re-done by calling applyGraphChange. They can be undone + by calling applyGraphChange on [change inverse]. + */ +@interface Graph : NSObject <NSCopying> { + NSRecursiveLock *graphLock; + BOOL dirty; // keep track of when inEdges and outEdges need an update + NSMutableArray *nodes; + NSMutableArray *edges; + + NSMapTable *inEdges; + NSMapTable *outEdges; + + GraphElementData *data; + NSRect boundingBox; +} + +/*! + @property data + @brief Data associated with the graph. + */ +@property (copy) GraphElementData *data; + +// KVC methods +- (void) insertObject:(GraphElementProperty*)gep + inDataAtIndex:(NSUInteger)index; +- (void) removeObjectFromDataAtIndex:(NSUInteger)index; +- (void) replaceObjectInDataAtIndex:(NSUInteger)index + withObject:(GraphElementProperty*)gep; + +/*! + @property nodes + @brief The set of nodes. + @details The node set is cached internally, so no need to lock + the graph when enumerating. + */ +@property (readonly) NSArray *nodes; + +/*! + @property edges + @brief The set of edges. + @details The edge set is cached internally, so no need to lock + the graph when enumerating. + */ +@property (readonly) NSArray *edges; + +/*! + @property boundingBox + @brief The bounding box of a graph + @details Optional data containing the bounding box, set with + \path [use as bounding box] .... + */ +@property (assign) NSRect boundingBox; + +/*! + @property hasBoundingBox + @brief Returns true if this graph has a bounding box. + */ +@property (readonly) BOOL hasBoundingBox; + + +/*! + @brief Computes graph bounds. + @result Graph bounds. + */ +- (NSRect)bounds; + +/*! + @brief Returns the set of edges incident to the given node set. + @param nds a set of nodes. + @result A set of incident edges. + */ +- (NSSet*)incidentEdgesForNodes:(NSSet*)nds; + +/*! + @brief Returns the set of in-edges for this node. + @param nd a node. + @result A set of edges. +*/ +- (NSSet*)inEdgesForNode:(Node*)nd; + +/*! + @brief Returns the set of out-edges for this node. + @param nd a node. + @result A set of edges. +*/ +- (NSSet*)outEdgesForNode:(Node*)nd; + +/*! + @brief Gives a copy of the full subgraph with the given nodes. + @param nds a set of nodes. + @result A subgraph. + */ +- (Graph*)copyOfSubgraphWithNodes:(NSSet*)nds; + +/*! + @brief Gives a copy of the full subgraph with the given nodes. + @param nds a set of nodes. + @param zone an allocation zone + @result A subgraph. + */ +- (Graph*)copyOfSubgraphWithNodes:(NSSet*)nds zone:(NSZone*)zone; + +/*! + @brief Gives a set of edge-arrays that partition all of the edges in the graph. + @result An NSet of NSArrays of edges. + */ +- (NSSet*)pathCover; + +/*! + @brief Adds a node. + @param node + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)addNode:(Node*)node; + +/*! + @brief Removes a node. + @param node + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)removeNode:(Node*)node; + +/*! + @brief Removes a set of nodes. + @param nds a set of nodes + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)removeNodes:(NSSet *)nds; + +/*! + @brief Adds an edge. + @param edge + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)addEdge:(Edge*)edge; + +/*! + @brief Removed an edge. + @param edge + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)removeEdge:(Edge*)edge; + +/*! + @brief Removes a set of edges. + @param es a set of edges. + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)removeEdges:(NSSet *)es; + +/*! + @brief Convenience function, intializes an edge with the given + source and target and adds it. + @param source the source of the edge. + @param target the target of the edge. + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)addEdgeFrom:(Node*)source to:(Node*)target; + +/*! + @brief Return the z-index for a given node (lower is farther back). + @param node a node in the graph + @result An <tt>int</tt> + */ +- (int)indexOfNode:(Node*)node; + +/*! + @brief Set the z-index for a given node (lower is farther back). + @param idx a new z-index + @param node a node in the graph + */ +- (void)setIndex:(int)idx ofNode:(Node*)node; + +/*! + @brief Bring set of nodes forward by one. + @param nodeSet a set of nodes + */ +- (GraphChange*)bringNodesForward:(NSSet*)nodeSet; + +/*! + @brief Bring set of nodes to the front. + @param nodeSet a set of nodes + */ +- (GraphChange*)bringNodesToFront:(NSSet*)nodeSet; + +/*! + @brief Bring set of edges to the front. + @param edgeSet a set of edges + */ +- (GraphChange*)bringEdgesToFront:(NSSet*)edgeSet; + +/*! + @brief Bring set of edges forward by one. + @param edgeSet a set of edges + */ +- (GraphChange*)bringEdgesForward:(NSSet*)edgeSet; + +/*! + @brief Send set of nodes backward by one. + @param nodeSet a set of nodes + */ +- (GraphChange*)sendNodesBackward:(NSSet*)nodeSet; + +/*! + @brief Send set of edges backward by one. + @param edgeSet a set of edges + */ +- (GraphChange*)sendEdgesBackward:(NSSet*)edgeSet; + +/*! + @brief Send set of nodes to back. + @param nodeSet a set of nodes + */ +- (GraphChange*)sendNodesToBack:(NSSet*)nodeSet; + +/*! + @brief Send set of edges to back. + @param edgeSet a set of edges + */ +- (GraphChange*)sendEdgesToBack:(NSSet*)edgeSet; + + +/*! + @brief Transform every node in the graph to screen space. + @param t a transformer + */ +- (void)applyTransformer:(Transformer*)t; + +/*! + @brief Shift nodes by a given distance. + @param ns a set of nodes. + @param p an x and y distance, given as an NSPoint. + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)shiftNodes:(id<NSFastEnumeration>)ns byPoint:(NSPoint)p; + +/*! + @brief Reverse the given edges + @param es the edges to reverse + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)reverseEdges:(NSSet *)es; + +/*! + @brief Insert the given graph into this one. Used for copy + and paste. + @param g a graph. + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)insertGraph:(Graph*)g; + +/*! + @brief Flip the subgraph defined by the given node set + horizontally. + @param nds a set of nodes. + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)flipHorizontalNodes:(NSSet*)nds; + +/*! + @brief Flip the subgraph defined by the given node set + vertically. + @param nds a set of nodes. + @result A <tt>GraphChange</tt> recording this action. + */ +- (GraphChange*)flipVerticalNodes:(NSSet*)nds; + +/*! + @brief Apply a graph change. + @details An undo manager should maintain a stack of GraphChange + objects returned. To undo a GraphChange, call this method + with <tt>[change inverse]</tt> as is argument. + @param ch a graph change. + */ +- (void)applyGraphChange:(GraphChange*)ch; + +/*! + @brief The TikZ representation of this graph. + @details The TikZ representation of this graph. The TikZ code should + contain enough data to totally reconstruct the graph. + @result A string containing TikZ code. + */ +- (NSString*)tikz; + + +/*! + @brief Copy the node set and return a table of copies, whose + keys are the original nodes. This is used to save the state + of a set of nodes in a GraphChange. + @param nds a set of nodes. + @result A <tt>NSMapTable</tt> of node copies. + */ ++ (NSMapTable*)nodeTableForNodes:(NSSet*)nds; + ++ (NSMapTable*)nodeTableForNodes:(NSSet*)nds withZone:(NSZone*)zone; + +/*! + @brief Copy the edge set and return a table of copies, whose + keys are the original edges. This is used to save the state + of a set of edges in a GraphChange. + @param es a set of edges. + @result A <tt>NSMapTable</tt> of edge copies. + */ ++ (NSMapTable*)edgeTableForEdges:(NSSet*)es; + ++ (NSMapTable*)edgeTableForEdges:(NSSet*)es withZone:(NSZone*)zone; + +/*! + @brief Compute the bounds for a set of nodes. + @param nds an enumerable collection of nodes. + @result The bounds. + */ ++ (NSRect)boundsForNodes:(id<NSFastEnumeration>)nds; + +/*! + @brief Factory method for constructing graphs. + @result An empty graph. + */ ++ (Graph*)graph; + +/** + * Initialize an empty graph + */ +- (id)init; + +/** + * Constructs a graph from the given tikz code + * + * See TikzGraphAssembler for more information about the error argument. + */ ++ (Graph*)graphFromTikz:(NSString*)tikz error:(NSError**)e; + +/** + * Constructs a graph from the given tikz code + */ ++ (Graph*)graphFromTikz:(NSString*)tikz; + +/** + * Initialize an empty graph from the given tikz code + * + * Note that this may not return the same object it was called on, + * and will return nil if parsing failed. + * + * See TikzGraphAssembler for more information about the error argument. + */ +- (id)initFromTikz:(NSString*)tikz error:(NSError**)e; + +/** + * Initialize an empty graph from the given tikz code + * + * Note that this may not return the same object it was called on, + * and will return nil if parsing failed. + */ +- (id)initFromTikz:(NSString*)tikz; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Graph.m b/tikzit-1/src/common/Graph.m new file mode 100644 index 0000000..cf09a69 --- /dev/null +++ b/tikzit-1/src/common/Graph.m @@ -0,0 +1,922 @@ +// +// Graph.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "Graph.h" +#import "TikzGraphAssembler.h" +#import "Shape.h" + +@interface Graph (Private) +- (void) shapeDictionaryReplaced:(NSNotification*)notification; +@end + +@implementation Graph + +- (id)init { + self = [super init]; + if (self != nil) { + data = [[GraphElementData alloc] init]; + boundingBox = NSMakeRect(0, 0, 0, 0); + graphLock = [[NSRecursiveLock alloc] init]; + nodes = [[NSMutableArray alloc] initWithCapacity:10]; + edges = [[NSMutableArray alloc] initWithCapacity:10]; + inEdges = nil; + outEdges = nil; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(shapeDictionaryReplaced:) + name:@"ShapeDictionaryReplaced" + object:[Shape class]]; + } + return self; +} + +- (id)initFromTikz:(NSString*)tikz error:(NSError**)e { +#if __has_feature(objc_arc) + return [TikzGraphAssembler parseTikz:tikz error:e]; +#else + [self release]; + return [[TikzGraphAssembler parseTikz:tikz error:e] retain]; +#endif +} + +- (id)initFromTikz:(NSString*)tikz { + return [self initFromTikz:tikz error:NULL]; +} + +- (id) copyWithZone:(NSZone*)zone { + Graph *newGraph = [self copyOfSubgraphWithNodes:[NSSet setWithArray:nodes] zone:zone]; + [newGraph setData:[self data]]; + return newGraph; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [graphLock lock]; +#if ! __has_feature(objc_arc) + [inEdges release]; + [outEdges release]; + [edges release]; + [nodes release]; + [data release]; + [graphLock unlock]; + [graphLock release]; + + [super dealloc]; +#endif +} + +- (void)sync { + [graphLock lock]; + if (dirty) { +#if ! __has_feature(objc_arc) + [inEdges release]; + [outEdges release]; +#endif + inEdges = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory capacity:10]; + outEdges = [[NSMapTable alloc] initWithKeyOptions:NSMapTableStrongMemory valueOptions:NSMapTableStrongMemory capacity:10]; + +#if ! __has_feature(objc_arc) + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; +#endif + + for (Edge *e in edges) { + NSMutableSet *ie = [inEdges objectForKey:[e target]]; + NSMutableSet *oe = [outEdges objectForKey:[e source]]; + + if (ie == nil) { + ie = [NSMutableSet setWithCapacity:4]; + [inEdges setObject:ie forKey:[e target]]; + } + + if (oe == nil) { + oe = [NSMutableSet setWithCapacity:4]; + [outEdges setObject:oe forKey:[e source]]; + } + + [ie addObject:e]; + [oe addObject:e]; + } + +#if ! __has_feature(objc_arc) + [pool drain]; +#endif + + dirty = NO; + } + [graphLock unlock]; +} + +@synthesize nodes; +@synthesize edges; + +@synthesize data; +- (void) insertObject:(GraphElementProperty*)gep + inDataAtIndex:(NSUInteger)index { + [data insertObject:gep atIndex:index]; +} +- (void) removeObjectFromDataAtIndex:(NSUInteger)index { + [data removeObjectAtIndex:index]; +} +- (void) replaceObjectInDataAtIndex:(NSUInteger)index + withObject:(GraphElementProperty*)gep { + [data replaceObjectAtIndex:index withObject:gep]; +} + +@synthesize boundingBox; + +- (NSRect)bounds { + [graphLock lock]; + NSRect b = [Graph boundsForNodes:nodes]; + [graphLock unlock]; + return b; +} + +- (BOOL)hasBoundingBox { + return !( + boundingBox.size.width == 0 && + boundingBox.size.height == 0 + ); +} + +- (NSSet*)inEdgesForNode:(Node*)nd { + [self sync]; +#if __has_feature(objc_arc) + return [inEdges objectForKey:nd]; +#else + return [[[inEdges objectForKey:nd] retain] autorelease]; +#endif +} + +- (NSSet*)outEdgesForNode:(Node*)nd { + [self sync]; +#if __has_feature(objc_arc) + return [outEdges objectForKey:nd]; +#else + return [[[outEdges objectForKey:nd] retain] autorelease]; +#endif +} + +- (NSSet*)incidentEdgesForNodes:(NSSet*)nds { + [self sync]; + + NSMutableSet *mset = [NSMutableSet setWithCapacity:10]; + for (Node *n in nds) { + [mset unionSet:[self inEdgesForNode:n]]; + [mset unionSet:[self outEdgesForNode:n]]; + } + + return mset; +} + +- (void)applyTransformer:(Transformer *)t { + [graphLock lock]; + for (Node *n in nodes) { + [n setPoint:[t toScreen:[n point]]]; + } + [graphLock unlock]; +} + +- (GraphChange*)addNode:(Node *)node{ + [graphLock lock]; + NSSet *addedNode; + + // addNode is a no-op if graph already contains node + if (![nodes containsObject:node]) { + [nodes addObject:node]; + dirty = YES; + addedNode = [NSSet setWithObject:node]; + } else { + addedNode = [NSSet set]; + } + [graphLock unlock]; + + return [GraphChange graphAdditionWithNodes:addedNode + edges:[NSSet set]]; +} + +- (GraphChange*)removeNode:(Node*)node { + [graphLock lock]; + NSMutableSet *affectedEdges = [NSMutableSet set]; + for (Edge *e in edges) { + if ([e source] == node || [e target] == node) { + [affectedEdges addObject:e]; + } + } + for (Edge *e in affectedEdges) { + [edges removeObject:e]; + } + [nodes removeObject:node]; + dirty = YES; + [graphLock unlock]; + + return [GraphChange graphDeletionWithNodes:[NSSet setWithObject:node] + edges:affectedEdges]; +} + +- (GraphChange*)removeNodes:(NSSet *)nds { + [graphLock lock]; + + Node *n; + Edge *e; + + NSMutableSet *affectedEdges = [NSMutableSet set]; + NSEnumerator *en = [edges objectEnumerator]; + while ((e = [en nextObject])) { + if ([nds containsObject:[e source]] || [nds containsObject:[e target]]) { + [affectedEdges addObject:e]; + } + } + + en = [affectedEdges objectEnumerator]; + while ((e = [en nextObject])) [edges removeObject:e]; + + en = [nds objectEnumerator]; + while ((n = [en nextObject])) [nodes removeObject:n]; + + dirty = YES; + [graphLock unlock]; + + return [GraphChange graphDeletionWithNodes:nds edges:affectedEdges]; +} + +- (GraphChange*)addEdge:(Edge*)edge { + [graphLock lock]; + NSSet *addedEdge; + + // addEdge is a no-op if graph already contains edge + if (![edges containsObject:edge]) { + [edges addObject:edge]; + dirty = YES; + addedEdge = [NSSet setWithObject:edge]; + } else { + addedEdge = [NSSet set]; + } + [graphLock unlock]; + + return [GraphChange graphAdditionWithNodes:[NSSet set] + edges:addedEdge]; +} + +- (GraphChange*)removeEdge:(Edge *)edge { + [graphLock lock]; + [edges removeObject:edge]; + dirty = YES; + [graphLock unlock]; + return [GraphChange graphDeletionWithNodes:[NSSet set] + edges:[NSSet setWithObject:edge]]; +} + +- (GraphChange*)removeEdges:(NSSet *)es { + [graphLock lock]; + + for (Edge *e in es) { + [edges removeObject:e]; + } + dirty = YES; + [graphLock unlock]; + return [GraphChange graphDeletionWithNodes:[NSSet set] edges:es]; +} + +- (GraphChange*)addEdgeFrom:(Node *)source to:(Node *)target { + return [self addEdge:[Edge edgeWithSource:source andTarget:target]]; +} + +- (GraphChange*)shiftNodes:(id<NSFastEnumeration>)ns byPoint:(NSPoint)p { + NSPoint newLoc; + NSMutableSet *nodeSet = [NSMutableSet setWithCapacity:5]; + for (Node *n in ns) { + newLoc = NSMakePoint([n point].x + p.x, [n point].y + p.y); + [n setPoint:newLoc]; + [nodeSet addObject:n]; + } + return [GraphChange shiftNodes:nodeSet byPoint:p]; +} + +- (GraphChange*)reverseEdges:(NSSet *)es { + [graphLock lock]; + for (Edge *e in es) { + [e reverse]; + } + dirty = YES; + [graphLock unlock]; + return [GraphChange reverseEdges:es]; +} + +- (int)indexOfNode:(Node *)node { + return [nodes indexOfObject:node]; +} + +- (void)setIndex:(int)idx ofNode:(Node *)node { + [graphLock lock]; + + if ([nodes containsObject:node]) { + [nodes removeObject:node]; + [nodes insertObject:node atIndex:idx]; + } + + [graphLock unlock]; +} + +- (int)indexOfEdge:(Edge *)edge { + return [edges indexOfObject:edge]; +} + +- (void)setIndex:(int)idx ofEdge:(Edge *)edge { + [graphLock lock]; + + if ([edges containsObject:edge]) { + [edges removeObject:edge]; + [edges insertObject:edge atIndex:idx]; + } + + [graphLock unlock]; +} + +- (GraphChange*)bringNodesForward:(NSSet*)nodeSet { + NSArray *oldOrder = [nodes copy]; + [graphLock lock]; + // start at the top of the array and work backwards + for (int i = [nodes count]-2; i >= 0; --i) { + if ( [nodeSet containsObject:[nodes objectAtIndex:i]] && + ![nodeSet containsObject:[nodes objectAtIndex:i+1]]) + { + [self setIndex:(i+1) ofNode:[nodes objectAtIndex:i]]; + } + } + GraphChange *change = [GraphChange nodeOrderChangeFrom:oldOrder to:nodes moved:nodeSet]; + [graphLock unlock]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)bringNodesToFront:(NSSet*)nodeSet { + NSArray *oldOrder = [nodes copy]; + int i = 0, top = [nodes count]-1; + + while (i <= top) { + if ([nodeSet containsObject:[nodes objectAtIndex:i]]) { + [self setIndex:([nodes count]-1) ofNode:[nodes objectAtIndex:i]]; + --top; + } else { + ++i; + } + } + GraphChange *change = [GraphChange nodeOrderChangeFrom:oldOrder to:nodes moved:nodeSet]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)bringEdgesForward:(NSSet*)edgeSet { + [graphLock lock]; + NSArray *oldOrder = [edges copy]; + // start at the top of the array and work backwards + for (int i = [edges count]-2; i >= 0; --i) { + if ( [edgeSet containsObject:[edges objectAtIndex:i]] && + ![edgeSet containsObject:[edges objectAtIndex:i+1]]) + { + [self setIndex:(i+1) ofEdge:[edges objectAtIndex:i]]; + } + } + GraphChange *change = [GraphChange edgeOrderChangeFrom:oldOrder to:edges moved:edgeSet]; + [graphLock unlock]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)bringEdgesToFront:(NSSet*)edgeSet { + NSArray *oldOrder = [edges copy]; + int i = 0, top = [edges count]-1; + + while (i <= top) { + if ([edgeSet containsObject:[edges objectAtIndex:i]]) { + [self setIndex:([edges count]-1) ofEdge:[edges objectAtIndex:i]]; + --top; + } else { + ++i; + } + } + GraphChange *change = [GraphChange edgeOrderChangeFrom:oldOrder to:edges moved:edgeSet]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)sendNodesBackward:(NSSet*)nodeSet { + [graphLock lock]; + NSArray *oldOrder = [nodes copy]; + // start at the top of the array and work backwards + for (int i = 1; i < [nodes count]; ++i) { + if ( [nodeSet containsObject:[nodes objectAtIndex:i]] && + ![nodeSet containsObject:[nodes objectAtIndex:i-1]]) + { + [self setIndex:(i-1) ofNode:[nodes objectAtIndex:i]]; + } + } + GraphChange *change = [GraphChange nodeOrderChangeFrom:oldOrder to:nodes moved:nodeSet]; + [graphLock unlock]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)sendEdgesBackward:(NSSet*)edgeSet { + [graphLock lock]; + NSArray *oldOrder = [edges copy]; + // start at the top of the array and work backwards + for (int i = 1; i < [edges count]; ++i) { + if ( [edgeSet containsObject:[edges objectAtIndex:i]] && + ![edgeSet containsObject:[edges objectAtIndex:i-1]]) + { + [self setIndex:(i-1) ofEdge:[edges objectAtIndex:i]]; + } + } + GraphChange *change = [GraphChange edgeOrderChangeFrom:oldOrder to:edges moved:edgeSet]; + [graphLock unlock]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)sendNodesToBack:(NSSet*)nodeSet { + NSArray *oldOrder = [nodes copy]; + int i = [nodes count]-1, bot = 0; + + while (i >= bot) { + if ([nodeSet containsObject:[nodes objectAtIndex:i]]) { + [self setIndex:0 ofNode:[nodes objectAtIndex:i]]; + ++bot; + } else { + --i; + } + } + GraphChange *change = [GraphChange nodeOrderChangeFrom:oldOrder to:nodes moved:nodeSet]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)sendEdgesToBack:(NSSet*)edgeSet { + NSArray *oldOrder = [edges copy]; + int i = [edges count]-1, bot = 0; + + while (i >= bot) { + if ([edgeSet containsObject:[edges objectAtIndex:i]]) { + [self setIndex:0 ofEdge:[edges objectAtIndex:i]]; + ++bot; + } else { + --i; + } + } + GraphChange *change = [GraphChange edgeOrderChangeFrom:oldOrder to:edges moved:edgeSet]; + +#if ! __has_feature(objc_arc) + [oldOrder release]; +#endif + + return change; +} + +- (GraphChange*)insertGraph:(Graph*)g { + [graphLock lock]; + + for (Node *n in [g nodes]) { + [self addNode:n]; + } + + for (Edge *e in [g edges]) { + [self addEdge:e]; + } + + dirty = YES; + + [graphLock unlock]; + + + return [GraphChange graphAdditionWithNodes:[NSSet setWithArray:[g nodes]] edges:[NSSet setWithArray:[g edges]]]; +} + +- (void)flipNodes:(NSSet*)nds horizontal:(BOOL)horiz { + [graphLock lock]; + + NSRect bds = [Graph boundsForNodes:nds]; + float ctr; + if (horiz) ctr = bds.origin.x + (bds.size.width/2); + else ctr = bds.origin.y + (bds.size.height/2); + + Node *n; + NSPoint p; + NSEnumerator *en = [nds objectEnumerator]; + while ((n = [en nextObject])) { + p = [n point]; + if (horiz) p.x = 2 * ctr - p.x; + else p.y = 2 * ctr - p.y; + [n setPoint:p]; + } + + Edge *e; + en = [edges objectEnumerator]; + while ((e = [en nextObject])) { + if ([nds containsObject:[e source]] && + [nds containsObject:[e target]]) + { + if ([e bendMode] == EdgeBendModeInOut) { + if (horiz) { + if ([e inAngle] < 0) [e setInAngle:(-180 - [e inAngle])]; + else [e setInAngle:180 - [e inAngle]]; + + if ([e outAngle] < 0) [e setOutAngle:(-180 - [e outAngle])]; + else [e setOutAngle:180 - [e outAngle]]; + } else { + [e setInAngle:-[e inAngle]]; + [e setOutAngle:-[e outAngle]]; + } + } else { + [e setBend:-[e bend]]; + } + } + } + + [graphLock unlock]; +} + +- (GraphChange*)flipHorizontalNodes:(NSSet*)nds { + [self flipNodes:nds horizontal:YES]; + return [GraphChange flipNodes:nds horizontal:YES]; +} + +- (GraphChange*)flipVerticalNodes:(NSSet*)nds { + [self flipNodes:nds horizontal:NO]; + return [GraphChange flipNodes:nds horizontal:NO]; +} + +- (Graph*)copyOfSubgraphWithNodes:(NSSet*)nds { + return [self copyOfSubgraphWithNodes:nds zone:NSDefaultMallocZone()]; +} + +- (Graph*)copyOfSubgraphWithNodes:(NSSet*)nds zone:(NSZone*)zone { + [graphLock lock]; + + NSMapTable *newNds = [Graph nodeTableForNodes:nds withZone:zone]; + Graph* newGraph = [[Graph allocWithZone:zone] init]; + + for (Node *nd in [newNds objectEnumerator]) { + [newGraph addNode:nd]; + } + + for (Edge *e in edges) { + if ([nds containsObject:[e source]] && [nds containsObject:[e target]]) { + Edge *e1 = [e copyWithZone:zone]; + [e1 setSource:[newNds objectForKey:[e source]]]; + [e1 setTarget:[newNds objectForKey:[e target]]]; + [newGraph addEdge:e1]; +#if ! __has_feature(objc_arc) + [e1 release]; // e1 belongs to newGraph +#endif + } + } + + [graphLock unlock]; + + return newGraph; +} + +- (NSSet*)pathCover { + [self sync]; + + NSMutableSet *cover = [NSMutableSet set]; +#if ! __has_feature(objc_arc) + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; +#endif + NSMutableSet *remainingEdges = [NSMutableSet setWithArray:edges]; + + while ([remainingEdges count] != 0) { + NSMutableArray *path = [[NSMutableArray alloc] init]; + NSSet *succs; + Edge *succ; + NSEnumerator *en; + + Edge *e = [remainingEdges anyObject]; + + while (e!=nil) { + [path addObject:e]; + [remainingEdges removeObject:e]; + + succs = [self outEdgesForNode:[e target]]; + en = [succs objectEnumerator]; + e = nil; + + while ((succ = [en nextObject])) { + if ([remainingEdges containsObject:succ]) e = succ; + } + } + + [cover addObject:path]; +#if ! __has_feature(objc_arc) + [path release]; +#endif + } + +#if ! __has_feature(objc_arc) + [pool drain]; +#endif + return cover; +} + +- (void)applyGraphChange:(GraphChange*)ch { + [graphLock lock]; + switch ([ch changeType]) { + case GraphAddition: + for (Node *n in [[ch affectedNodes] objectEnumerator]) { + [nodes addObject:n]; + } + + for (Edge *e in [[ch affectedEdges] objectEnumerator]) { + [edges addObject:e]; + } + + break; + case GraphDeletion: + for (Edge *e in [[ch affectedEdges] objectEnumerator]) { + [edges removeObject:e]; + } + + for (Node *n in [[ch affectedNodes] objectEnumerator]) { + [nodes removeObject:n]; + } + + break; + case NodePropertyChange: + [[ch nodeRef] setPropertiesFromNode:[ch nwNode]]; + break; + case NodesPropertyChange: + for (Node *key in [[ch nwNodeTable] keyEnumerator]) { + [key setPropertiesFromNode:[[ch nwNodeTable] objectForKey:key]]; + } + break; + case EdgePropertyChange: + [[ch edgeRef] setPropertiesFromEdge:[ch nwEdge]]; + break; + case EdgesPropertyChange: + for (Edge *key in [[ch nwEdgeTable] keyEnumerator]) { + [key setPropertiesFromEdge:[[ch nwEdgeTable] objectForKey:key]]; + } + break; + case NodesShift: + for (Node *n in [[ch affectedNodes] objectEnumerator]) { + [n setPoint:NSMakePoint([n point].x + [ch shiftPoint].x, + [n point].y + [ch shiftPoint].y)]; + } + break; + case NodesFlip: + [self flipNodes:[ch affectedNodes] horizontal:[ch horizontal]]; + break; + case EdgesReverse: + for (Edge *e in [[ch affectedEdges] objectEnumerator]) { + [e reverse]; + } + break; + case BoundingBoxChange: + [self setBoundingBox:[ch nwBoundingBox]]; + break; + case GraphPropertyChange: + [data setArray:[ch nwGraphData]]; + break; + case NodeOrderChange: + [nodes setArray:[ch nwNodeOrder]]; + break; + case EdgeOrderChange: + [edges setArray:[ch nwEdgeOrder]]; + break; + } + + dirty = YES; + [graphLock unlock]; +} + +//- (void)undoGraphChange:(GraphChange*)ch { +// [self applyGraphChange:[GraphChange inverseGraphChange:ch]]; +//} + +- (NSString*)tikz { + [graphLock lock]; + + NSMutableString *code = [NSMutableString + stringWithFormat:@"\\begin{tikzpicture}%@\n", + [[self data] tikzList]]; + + if ([self hasBoundingBox]) { + [code appendFormat:@"\t\\path [use as bounding box] (%@,%@) rectangle (%@,%@);\n", + [NSNumber numberWithFloat:boundingBox.origin.x], + [NSNumber numberWithFloat:boundingBox.origin.y], + [NSNumber numberWithFloat:boundingBox.origin.x + boundingBox.size.width], + [NSNumber numberWithFloat:boundingBox.origin.y + boundingBox.size.height]]; + } + +// NSArray *sortedNodeList = [[nodes allObjects] +// sortedArrayUsingSelector:@selector(compareTo:)]; + //NSMutableDictionary *nodeNames = [NSMutableDictionary dictionary]; + + if ([nodes count] > 0) [code appendFormat:@"\t\\begin{pgfonlayer}{nodelayer}\n"]; + + int i = 0; + for (Node *n in nodes) { + [n updateData]; + [n setName:[NSString stringWithFormat:@"%d", i]]; + [code appendFormat:@"\t\t\\node %@ (%d) at (%@, %@) {%@};\n", + [[n data] tikzList], + i, + formatFloat([n point].x, 4), + formatFloat([n point].y, 4), + [n label] + ]; + i++; + } + + if ([nodes count] > 0) [code appendFormat:@"\t\\end{pgfonlayer}\n"]; + if ([edges count] > 0) [code appendFormat:@"\t\\begin{pgfonlayer}{edgelayer}\n"]; + + NSString *nodeStr; + for (Edge *e in edges) { + [e updateData]; + + if ([e hasEdgeNode]) { + nodeStr = [NSString stringWithFormat:@"node%@{%@} ", + [[[e edgeNode] data] tikzList], + [[e edgeNode] label] + ]; + } else { + nodeStr = @""; + } + + NSString *edata = [[e data] tikzList]; + + NSString *srcAnchor; + NSString *tgtAnchor; + + if ([[e sourceAnchor] isEqual:@""]) { + srcAnchor = @""; + } else { + srcAnchor = [NSString stringWithFormat:@".%@", [e sourceAnchor]]; + } + + if ([[e targetAnchor] isEqual:@""]) { + tgtAnchor = @""; + } else { + tgtAnchor = [NSString stringWithFormat:@".%@", [e targetAnchor]]; + } + + [code appendFormat:@"\t\t\\draw%@ (%@%@) to %@(%@%@);\n", + ([edata isEqual:@""]) ? @"" : [NSString stringWithFormat:@" %@", edata], + [[e source] name], + srcAnchor, + nodeStr, + ([e source] == [e target]) ? @"" : [[e target] name], + tgtAnchor + ]; + } + + if ([edges count] > 0) [code appendFormat:@"\t\\end{pgfonlayer}\n"]; + + [code appendString:@"\\end{tikzpicture}"]; + + [graphLock unlock]; + + return code; +} + ++ (Graph*)graph { +#if __has_feature(objc_arc) + return [[self alloc] init]; +#else + return [[[self alloc] init] autorelease]; +#endif +} + ++ (Graph*)graphFromTikz:(NSString*)tikz error:(NSError**)e { + return [TikzGraphAssembler parseTikz:tikz error:e]; +} + ++ (Graph*)graphFromTikz:(NSString*)tikz { + return [self graphFromTikz:tikz error:NULL]; +} + ++ (NSMapTable*)nodeTableForNodes:(NSSet*)nds { + return [self nodeTableForNodes:nds withZone:NSDefaultMallocZone()]; +} + ++ (NSMapTable*)nodeTableForNodes:(NSSet*)nds withZone:(NSZone*)zone { + NSMapTable *tab = [[NSMapTable allocWithZone:zone] + initWithKeyOptions:NSMapTableStrongMemory + valueOptions:NSMapTableStrongMemory + capacity:[nds count]]; + for (Node *n in nds) { + Node *ncopy = [n copyWithZone:zone]; + [tab setObject:ncopy forKey:n]; +#if ! __has_feature(objc_arc) + [ncopy release]; // tab should still retain ncopy. +#endif + } +#if __has_feature(objc_arc) + return tab; +#else + return [tab autorelease]; +#endif +} + ++ (NSMapTable*)edgeTableForEdges:(NSSet*)es { + return [self edgeTableForEdges:es withZone:NSDefaultMallocZone()]; +} + ++ (NSMapTable*)edgeTableForEdges:(NSSet*)es withZone:(NSZone*)zone { + NSMapTable *tab = [[NSMapTable allocWithZone:zone] + initWithKeyOptions:NSMapTableStrongMemory + valueOptions:NSMapTableStrongMemory + capacity:[es count]]; + for (Edge *e in es) { + Edge *ecopy = [e copyWithZone:zone]; + [tab setObject:ecopy forKey:e]; +#if ! __has_feature(objc_arc) + [ecopy release]; // tab should still retain ecopy. +#endif + } + return tab; +} + + ++ (NSRect)boundsForNodes:(id<NSFastEnumeration>)nds { + NSPoint tl, br; + NSPoint p; + BOOL hasPoints = NO; + for (Node *n in nds) { + p = [n point]; + if (!hasPoints) { + tl = p; + br = p; + hasPoints = YES; + } else { + if (p.x < tl.x) tl.x = p.x; + if (p.y > tl.y) tl.y = p.y; + if (p.x > br.x) br.x = p.x; + if (p.y < br.y) br.y = p.y; + } + } + + return (hasPoints) ? NSRectAroundPoints(tl, br) : NSMakeRect(0, 0, 0, 0); +} + +@end + +@implementation Graph (Private) +- (void) shapeDictionaryReplaced:(NSNotification*)notification { + for (Edge *e in edges) { + [e recalculateProperties]; + } +} +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/GraphChange.h b/tikzit-1/src/common/GraphChange.h new file mode 100644 index 0000000..0e71a90 --- /dev/null +++ b/tikzit-1/src/common/GraphChange.h @@ -0,0 +1,344 @@ +// +// GraphChange.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "Node.h" +#import "Edge.h" + +typedef enum { + GraphAddition, + GraphDeletion, + NodePropertyChange, + EdgePropertyChange, + NodesPropertyChange, + EdgesPropertyChange, + NodesShift, + NodesFlip, + EdgesReverse, + BoundingBoxChange, + GraphPropertyChange, + NodeOrderChange, + EdgeOrderChange +} ChangeType; + +/*! + @class GraphChange + @brief Store the data associated with a graph change. + @details All of the methods that change a graph return an object of type GraphChange. + Graph changes can be re-done by calling [graph applyGraphChange:]. They can be undone + by calling applyGraphChange on [change inverse]. This class has no public initializer, + so everything should be constructed by factory methods. + */ +@interface GraphChange : NSObject { + ChangeType changeType; + + // for addition, deletion, and shifts + NSSet *affectedNodes; + NSSet *affectedEdges; + NSPoint shiftPoint; + + // for flip + BOOL horizontal; + + // for property changes + Node *nodeRef; + Edge *edgeRef; + + Node *oldNode, *nwNode; + Edge *oldEdge, *nwEdge; + NSMapTable *oldNodeTable, *nwNodeTable; + NSMapTable *oldEdgeTable, *nwEdgeTable; + NSRect oldBoundingBox, nwBoundingBox; + GraphElementData *oldGraphData, *nwGraphData; + + NSArray *oldNodeOrder, *nwNodeOrder; + NSArray *oldEdgeOrder, *nwEdgeOrder; +} + +/*! + @property changeType + @brief Type of GraphChange. + */ +@property (assign) ChangeType changeType; + +/*! + @property shiftPoint + @brief A point storing a shifted distance. + */ +@property (assign) NSPoint shiftPoint; + +/*! + @property horizontal + @brief Flags whether nodes were flipped horizontally + */ +@property (assign) BOOL horizontal; + +/*! + @property affectedNodes + @brief A set of nodes affected by this change, may be undefined. + */ +@property (copy) NSSet *affectedNodes; + +/*! + @property affectedEdges + @brief A set of edges affected by this change, may be undefined. + */ +@property (copy) NSSet *affectedEdges; + +/*! + @property nodeRef + @brief A reference to a single node affected by this change, may be undefined. + */ +@property (retain) Node *nodeRef; + +/*! + @property oldNode + @brief A copy of the node pre-change. + */ +@property (copy) Node *oldNode; + +/*! + @property nwNode + @brief A copy of the node post-change. + */ +@property (copy) Node *nwNode; + +/*! + @property edgeRef + @brief A reference to a single edge affected by this change, may be undefined. + */ +@property (retain) Edge *edgeRef; + +/*! + @property oldEdge + @brief A copy of the edge pre-change. + */ +@property (copy) Edge *oldEdge; + +/*! + @property nwEdge + @brief A copy of the edge post-change. + */ +@property (copy) Edge *nwEdge; + +/*! + @property oldNodeTable + @brief A a table containing copies of a set of nodes pre-change. + */ +@property (retain) NSMapTable *oldNodeTable; + +/*! + @property nwNodeTable + @brief A a table containing copies of a set of nodes post-change. + */ +@property (retain) NSMapTable *nwNodeTable; + +/*! + @property oldEdgeTable + @brief A a table containing copies of a set of edges pre-change. + */ +@property (retain) NSMapTable *oldEdgeTable; + +/*! + @property nwEdgeTable + @brief A a table containing copies of a set of edges post-change. + */ +@property (retain) NSMapTable *nwEdgeTable; + +/*! + @property oldBoundingBox + @brief The old bounding box. + */ +@property (assign) NSRect oldBoundingBox; + +/*! + @property nwBoundingBox + @brief The new bounding box. + */ +@property (assign) NSRect nwBoundingBox; + +/*! + @property oldGraphData + @brief The old graph data. + */ +@property (copy) GraphElementData *oldGraphData; + +/*! + @property nwGraphData + @brief The new graph data. + */ +@property (copy) GraphElementData *nwGraphData; + +/*! + @property oldNodeOrder + @brief The old node list. + */ +@property (copy) NSArray *oldNodeOrder; + +/*! + @property nwNodeOrder + @brief The new node list. + */ +@property (copy) NSArray *nwNodeOrder; + +/*! + @property oldEdgeOrder + @brief The old edge list. + */ +@property (copy) NSArray *oldEdgeOrder; + +/*! + @property nwEdgeOrder + @brief The new edge list. + */ +@property (copy) NSArray *nwEdgeOrder; + +/*! + @brief Invert a GraphChange. + @details Invert a GraphChange. Calling [graph applyGraphChange:[[graph msg:...] invert]] + should leave the graph unchanged for any method of Graph that returns a + GraphChange. + @result The inverse of the current Graph Change. + */ +- (GraphChange*)invert; + +/*! + @brief Construct a graph addition. affectedNodes are the added nodes, + affectedEdges are the added edges. + @param ns a set of nodes. + @param es a set of edges. + @result A graph addition. + */ ++ (GraphChange*)graphAdditionWithNodes:(NSSet*)ns edges:(NSSet*)es; + +/*! + @brief Construct a graph deletion. affectedNodes are the deleted nodes, + affectedEdges are the deleted edges. + @param ns a set of nodes. + @param es a set of edges. + @result A graph deletion. + */ ++ (GraphChange*)graphDeletionWithNodes:(NSSet*)ns edges:(NSSet*)es; + +/*! + @brief Construct a property change of a single node. + @param nd the affected node. + @param old a copy of the node pre-change + @param nw a copy of the node post-change + @result A property change of a single node. + */ ++ (GraphChange*)propertyChangeOfNode:(Node*)nd fromOld:(Node*)old toNew:(Node*)nw; + +/*! + @brief Construct a property change of a single edge. + @param e the affected edge. + @param old a copy of the edge pre-change + @param nw a copy of the edge post-change + @result A property change of a single node. + */ ++ (GraphChange*)propertyChangeOfEdge:(Edge*)e fromOld:(Edge *)old toNew:(Edge *)nw; + +/*! + @brief Construct a property change of set of nodes. + @details Construct a property change of set of nodes. oldC and newC should be + constructed using the class method [Graph nodeTableForNodes:] before + and after the property change, respectively. The affected nodes are + keys(oldC) = keys(newC). + @param oldC a table of copies of nodes pre-change + @param newC a table of copies of nodes post-change + @result A property change of a set of nodes. + */ ++ (GraphChange*)propertyChangeOfNodesFromOldCopies:(NSMapTable*)oldC + toNewCopies:(NSMapTable*)newC; + +/*! + @brief Construct a property change of set of edges. + @details Construct a property change of set of edges. oldC and newC should be + constructed using the class method [Graph edgeTableForEdges:] before + and after the property change, respectively. The affected edges are + keys(oldC) = keys(newC). + @param oldC a table of copies of edges pre-change + @param newC a table of copies of edges post-change + @result A property change of a set of edges. + */ ++ (GraphChange*)propertyChangeOfEdgesFromOldCopies:(NSMapTable*)oldC + toNewCopies:(NSMapTable*)newC; + + +/*! + @brief Construct a shift of a set of nodes by a given point. + @param ns the affected nodes. + @param p a point storing (dx,dy) + @result A shift of a set of nodes. + */ ++ (GraphChange*)shiftNodes:(NSSet*)ns byPoint:(NSPoint)p; + +/*! + @brief Construct a horizontal or vertical flip of a set of nodes. + @param ns the affected nodes. + @param b flag for whether to flip horizontally + @result A flip of a set of nodes. + */ ++ (GraphChange*)flipNodes:(NSSet*)ns horizontal:(BOOL)b; + +/*! + @brief Construct a reversal of a set of edges. + @param es the affected edges. + @result A reverse of a set of edges. + */ ++ (GraphChange*)reverseEdges:(NSSet*)es; + +/*! + @brief Construct a bounding box change + @param oldBB the old bounding box + @param newBB the new bounding box + @result A bounding box change. + */ ++ (GraphChange*)changeBoundingBoxFrom:(NSRect)oldBB to:(NSRect)newBB; + +/*! + @brief Construct a graph property change + @param oldData the old graph data + @param newData the new graph data + @result A graph property change. + */ ++ (GraphChange*)propertyChangeOfGraphFrom:(GraphElementData*)oldData to:(GraphElementData*)newData; + +/*! + @brief Construct a node order change + @param old The old ordering + @param new The new ordering + @result A node order change + */ ++ (GraphChange*)nodeOrderChangeFrom:(NSArray*)old to:(NSArray*)new moved:(NSSet*)affected; + +/*! + @brief Construct an edge order change + @param old The old ordering + @param new The new ordering + @result A edge order change + */ ++ (GraphChange*)edgeOrderChangeFrom:(NSArray*)old to:(NSArray*)new moved:(NSSet*)affected; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/GraphChange.m b/tikzit-1/src/common/GraphChange.m new file mode 100644 index 0000000..29a3939 --- /dev/null +++ b/tikzit-1/src/common/GraphChange.m @@ -0,0 +1,369 @@ +// +// GraphChange.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + + +// GraphChange : store the data associated to a single, undo-able change +// to a graph. An undo manager should maintain a stack of such changes +// and undo/redo them on request using [graph applyGraphChange:...]. + +#import "GraphChange.h" + + +@implementation GraphChange + +- (id)init { + return [super init]; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [affectedNodes release]; + [affectedEdges release]; + [nodeRef release]; + [edgeRef release]; + [oldNode release]; + [nwNode release]; + [oldEdge release]; + [nwEdge release]; + [oldNodeTable release]; + [nwNodeTable release]; + [oldEdgeTable release]; + [nwEdgeTable release]; + [oldGraphData release]; + [nwGraphData release]; + [oldNodeOrder release]; + [nwNodeOrder release]; + [oldEdgeOrder release]; + [nwEdgeOrder release]; + + [super dealloc]; +#endif +} + +@synthesize changeType; +@synthesize shiftPoint, horizontal; +@synthesize affectedEdges, affectedNodes; +@synthesize edgeRef, nodeRef; +@synthesize nwNode, oldNode; +@synthesize nwEdge, oldEdge; +@synthesize oldNodeTable, nwNodeTable; +@synthesize oldEdgeTable, nwEdgeTable; +@synthesize oldBoundingBox, nwBoundingBox; +@synthesize oldGraphData, nwGraphData; +@synthesize oldNodeOrder, nwNodeOrder; +@synthesize oldEdgeOrder, nwEdgeOrder; + +- (GraphChange*)invert { + GraphChange *inverse = [[GraphChange alloc] init]; + [inverse setChangeType:[self changeType]]; + switch ([self changeType]) { + case GraphAddition: + [inverse setChangeType:GraphDeletion]; +#if __has_feature(objc_arc) + inverse->affectedNodes = affectedNodes; + inverse->affectedEdges = affectedEdges; +#else + inverse->affectedNodes = [affectedNodes retain]; + inverse->affectedEdges = [affectedEdges retain]; +#endif + break; + case GraphDeletion: + [inverse setChangeType:GraphAddition]; +#if __has_feature(objc_arc) + inverse->affectedNodes = affectedNodes; + inverse->affectedEdges = affectedEdges; +#else + inverse->affectedNodes = [affectedNodes retain]; + inverse->affectedEdges = [affectedEdges retain]; +#endif + break; + case NodePropertyChange: +#if __has_feature(objc_arc) + inverse->nodeRef = nodeRef; + inverse->oldNode = nwNode; + inverse->nwNode = oldNode; +#else + inverse->nodeRef = [nodeRef retain]; + inverse->oldNode = [nwNode retain]; + inverse->nwNode = [oldNode retain]; +#endif + break; + case NodesPropertyChange: +#if __has_feature(objc_arc) + +#else + inverse->oldNodeTable = [nwNodeTable retain]; + inverse->nwNodeTable = [oldNodeTable retain]; +#endif + break; + case EdgePropertyChange: +#if __has_feature(objc_arc) + inverse->edgeRef = edgeRef; + inverse->oldEdge = nwEdge; + inverse->nwEdge = oldEdge; +#else + inverse->edgeRef = [edgeRef retain]; + inverse->oldEdge = [nwEdge retain]; + inverse->nwEdge = [oldEdge retain]; +#endif + break; + case EdgesPropertyChange: +#if __has_feature(objc_arc) + inverse->oldEdgeTable = nwEdgeTable; + inverse->nwEdgeTable = oldEdgeTable; +#else + inverse->oldEdgeTable = [nwEdgeTable retain]; + inverse->nwEdgeTable = [oldEdgeTable retain]; +#endif + break; + case NodesShift: +#if __has_feature(objc_arc) + inverse->affectedNodes = affectedNodes; +#else + inverse->affectedNodes = [affectedNodes retain]; +#endif + [inverse setShiftPoint:NSMakePoint(-[self shiftPoint].x, + -[self shiftPoint].y)]; + break; + case NodesFlip: +#if __has_feature(objc_arc) + inverse->affectedNodes = affectedNodes; +#else + inverse->affectedNodes = [affectedNodes retain]; +#endif + [inverse setHorizontal:[self horizontal]]; + break; + case EdgesReverse: +#if __has_feature(objc_arc) + inverse->affectedEdges = affectedEdges; +#else + inverse->affectedEdges = [affectedEdges retain]; +#endif + break; + case BoundingBoxChange: + inverse->oldBoundingBox = nwBoundingBox; + inverse->nwBoundingBox = oldBoundingBox; + break; + case GraphPropertyChange: +#if __has_feature(objc_arc) + inverse->oldGraphData = nwGraphData; + inverse->nwGraphData = oldGraphData; +#else + inverse->oldGraphData = [nwGraphData retain]; + inverse->nwGraphData = [oldGraphData retain]; +#endif + break; + case NodeOrderChange: +#if __has_feature(objc_arc) + inverse->affectedNodes = affectedNodes; + inverse->oldNodeOrder = nwNodeOrder; + inverse->nwNodeOrder = oldNodeOrder; +#else + inverse->affectedNodes = [affectedNodes retain]; + inverse->oldNodeOrder = [nwNodeOrder retain]; + inverse->nwNodeOrder = [oldNodeOrder retain]; +#endif + break; + case EdgeOrderChange: +#if __has_feature(objc_arc) + inverse->affectedEdges = affectedEdges; + inverse->oldEdgeOrder = nwEdgeOrder; + inverse->nwEdgeOrder = oldEdgeOrder; +#else + inverse->affectedEdges = [affectedEdges retain]; + inverse->oldEdgeOrder = [nwEdgeOrder retain]; + inverse->nwEdgeOrder = [oldEdgeOrder retain]; +#endif + break; + } +#if __has_feature(objc_arc) + return inverse; +#else + return [inverse autorelease]; +#endif +} + ++ (GraphChange*)graphAdditionWithNodes:(NSSet *)ns edges:(NSSet *)es { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:GraphAddition]; + [gc setAffectedNodes:ns]; + [gc setAffectedEdges:es]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)graphDeletionWithNodes:(NSSet *)ns edges:(NSSet *)es { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:GraphDeletion]; + [gc setAffectedNodes:ns]; + [gc setAffectedEdges:es]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)propertyChangeOfNode:(Node*)nd fromOld:(Node*)old toNew:(Node*)nw { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:NodePropertyChange]; + [gc setNodeRef:nd]; + [gc setOldNode:old]; + [gc setNwNode:nw]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)propertyChangeOfNodesFromOldCopies:(NSMapTable*)oldC + toNewCopies:(NSMapTable*)newC { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:NodesPropertyChange]; + [gc setOldNodeTable:oldC]; + [gc setNwNodeTable:newC]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)propertyChangeOfEdge:(Edge*)e fromOld:(Edge *)old toNew:(Edge *)nw { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:EdgePropertyChange]; + [gc setEdgeRef:e]; + [gc setOldEdge:old]; + [gc setNwEdge:nw]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)propertyChangeOfEdgesFromOldCopies:(NSMapTable*)oldC + toNewCopies:(NSMapTable*)newC { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:EdgesPropertyChange]; + [gc setOldEdgeTable:oldC]; + [gc setNwEdgeTable:newC]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)shiftNodes:(NSSet*)ns byPoint:(NSPoint)p { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:NodesShift]; + [gc setAffectedNodes:ns]; + [gc setShiftPoint:p]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)flipNodes:(NSSet*)ns horizontal:(BOOL)b { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:NodesFlip]; + [gc setAffectedNodes:ns]; + [gc setHorizontal:b]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)reverseEdges:(NSSet*)es { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:EdgesReverse]; + [gc setAffectedEdges:es]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)changeBoundingBoxFrom:(NSRect)oldBB to:(NSRect)newBB { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:BoundingBoxChange]; + [gc setOldBoundingBox:oldBB]; + [gc setNwBoundingBox:newBB]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)propertyChangeOfGraphFrom:(GraphElementData*)oldData to:(GraphElementData*)newData { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:GraphPropertyChange]; + [gc setOldGraphData:oldData]; + [gc setNwGraphData:newData]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)nodeOrderChangeFrom:(NSArray*)old to:(NSArray*)new moved:(NSSet*)affected { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:NodeOrderChange]; + [gc setAffectedNodes:affected]; + [gc setOldNodeOrder:old]; + [gc setNwNodeOrder:new]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + ++ (GraphChange*)edgeOrderChangeFrom:(NSArray*)old to:(NSArray*)new moved:(NSSet*)affected { + GraphChange *gc = [[GraphChange alloc] init]; + [gc setChangeType:EdgeOrderChange]; + [gc setAffectedEdges:affected]; + [gc setOldEdgeOrder:old]; + [gc setNwEdgeOrder:new]; +#if __has_feature(objc_arc) + return gc; +#else + return [gc autorelease]; +#endif +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/GraphElementData.h b/tikzit-1/src/common/GraphElementData.h new file mode 100644 index 0000000..a65e6df --- /dev/null +++ b/tikzit-1/src/common/GraphElementData.h @@ -0,0 +1,94 @@ +// +// GraphElementData.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + + +#import <Foundation/Foundation.h> + +/*! + @class GraphElementData + @brief Store extra data associated with a graph, node, or edge. + @details Store the extra (style, ...) data associated with + a graph, node, or edge. This data is stored as a mutable + array of properties. It also implements hash-like accessors, + but care should be taken using these, as the list can contain + multiple occurrences of the same key. + + Convention: Getters and setters act on the *first* occurrence + of the key. 'Unsetters' remove *all* occurrences. + */ +@interface GraphElementData : NSMutableArray { + NSMutableArray *properties; +} + +- (id)init; ++ (id)data; + +/*! + @brief Set the given value for the *first* property matching this key. Add a + new property if it doesn't already exist. + @param val the value to set + @param key the key for this property + */ +- (void)setProperty:(NSString*)val forKey:(NSString*)key; + +/*! + @brief Remove *all* occurences of the property with the given key. + @param key + */ +- (void)unsetProperty:(NSString*)key; + +/*! + @brief Return the value of the *first* occurrence of the given key. + @param key + */ +- (NSString*)propertyForKey:(NSString*)key; + +/*! + @brief Add the given atom to the list, if it's not already present. + @param atom + */ +- (void)setAtom:(NSString*)atom; + +/*! + @brief Remove *all* occurrences of the given atom. + @param atom + */ +- (void)unsetAtom:(NSString*)atom; + +/*! + @brief Returns YES if the list contains at least one occurrence of + the given atom. + @param atom + @result A boolean value. + */ +- (BOOL)isAtomSet:(NSString*)atom; + +/*! + @brief Returns a TikZ-friendly string containing all of the properties. + @result A string. + */ +- (NSString*)tikzList; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/GraphElementData.m b/tikzit-1/src/common/GraphElementData.m new file mode 100644 index 0000000..41dc9aa --- /dev/null +++ b/tikzit-1/src/common/GraphElementData.m @@ -0,0 +1,188 @@ +// +// GraphElementData.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "GraphElementData.h" +#import "GraphElementProperty.h" + + +@implementation GraphElementData + ++ (id)data { +#if __has_feature(objc_arc) + return [[self alloc] init]; +#else + return [[[self alloc] init] autorelease]; +#endif +} + +- (id)init { + self = [super init]; + if (self) { + properties = [[NSMutableArray alloc] init]; + } + return self; +} + +// all of the array messages delegate to 'properties' + +- (NSUInteger)count { return [properties count]; } +- (id)objectAtIndex:(NSUInteger)index { + return [properties objectAtIndex:index]; +} +- (NSArray*)objectsAtIndexes:(NSIndexSet*)indexes { + return [properties objectsAtIndexes:indexes]; +} + +#if __has_feature(objc_arc) +- (void) getObjects:(__unsafe_unretained id*)buffer range:(NSRange)range { +#else +- (void) getObjects:(id*)buffer range:(NSRange)range { +#endif + [properties getObjects:buffer range:range]; +} + +- (void)insertObject:(id)anObject atIndex:(NSUInteger)index { + [properties insertObject:anObject atIndex:index]; +} +- (void)removeObjectAtIndex:(NSUInteger)index { + [properties removeObjectAtIndex:index]; +} +- (void)addObject:(id)anObject { + [properties addObject:anObject]; +} +- (void)removeLastObject { + [properties removeLastObject]; +} +- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject { + [properties replaceObjectAtIndex:index withObject:anObject]; +} + +#if __has_feature(objc_arc) +- (NSUInteger) countByEnumeratingWithState:(NSFastEnumerationState *)state + objects:(__unsafe_unretained id [])stackbuf + count:(NSUInteger)len { +#else +- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state + objects:(id *)stackbuf + count:(NSUInteger)len { +#endif + return [properties countByEnumeratingWithState:state objects:stackbuf count:len]; +} + +- (void)setProperty:(NSString*)val forKey:(NSString*)key { + GraphElementProperty *m = [[GraphElementProperty alloc] initWithKeyMatching:key]; + NSInteger idx = [properties indexOfObject:m]; +#if !__has_feature(objc_arc) + [m release]; +#endif + + GraphElementProperty *p; + if (idx == NSNotFound) { + p = [[GraphElementProperty alloc] initWithPropertyValue:val forKey:key]; + [properties addObject:p]; +#if !__has_feature(objc_arc) + [p release]; +#endif + } else { + p = [properties objectAtIndex:idx]; + [p setValue:val]; + } +} + +- (void)unsetProperty:(NSString*)key { + GraphElementProperty *m = [[GraphElementProperty alloc] initWithKeyMatching:key]; + [properties removeObject:m]; +#if !__has_feature(objc_arc) + [m release]; +#endif + +} + +- (NSString*)propertyForKey:(NSString*)key { + GraphElementProperty *m = [[GraphElementProperty alloc] initWithKeyMatching:key]; + NSInteger idx = [properties indexOfObject:m]; +#if !__has_feature(objc_arc) + [m release]; +#endif + + + if (idx == NSNotFound) { + return nil; + }else { + GraphElementProperty *p = [properties objectAtIndex:idx]; + return [p value]; + } +} + +- (void)setAtom:(NSString*)atom { + GraphElementProperty *a = [[GraphElementProperty alloc] initWithAtomName:atom]; + if (![properties containsObject:a]) [properties addObject:a]; +#if !__has_feature(objc_arc) + [a release]; +#endif +} + +- (void)unsetAtom:(NSString*)atom { + GraphElementProperty *a = [[GraphElementProperty alloc] initWithAtomName:atom]; + [properties removeObject:a]; +#if !__has_feature(objc_arc) + [a release]; +#endif +} + +- (BOOL)isAtomSet:(NSString*)atom { + GraphElementProperty *a = [[GraphElementProperty alloc] initWithAtomName:atom]; + BOOL set = [properties containsObject:a]; +#if !__has_feature(objc_arc) + [a release]; +#endif + return set; +} + +- (NSString*)tikzList { + NSString *s = [properties componentsJoinedByString:@", "]; + return ([s isEqualToString:@""]) ? @"" : [NSString stringWithFormat:@"[%@]", s]; +} + +- (id)copyWithZone:(NSZone *)zone { + GraphElementData *cp = [[GraphElementData allocWithZone:zone] init]; + for (GraphElementProperty *p in properties) { + GraphElementProperty *p2 = [p copy]; + [cp addObject:p2]; +#if !__has_feature(objc_arc) + [p2 release]; +#endif + } + return cp; +} + +- (void)dealloc { +#if !__has_feature(objc_arc) + [properties release]; + [super dealloc]; +#endif +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/GraphElementProperty.h b/tikzit-1/src/common/GraphElementProperty.h new file mode 100644 index 0000000..057cdbb --- /dev/null +++ b/tikzit-1/src/common/GraphElementProperty.h @@ -0,0 +1,88 @@ +// +// GraphElementProperty.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> + +/*! + @class GraphElementProperty + @brief A property. I.e. a single entry in a node's/edge's/graph's + GraphElementData table. + */ +@interface GraphElementProperty : NSObject<NSCopying> { + NSString *key; + NSString *value; + BOOL isAtom; + BOOL isKeyMatch; +} + +@property (copy) NSString *key; +@property (copy) NSString *value; +@property (readonly) BOOL isAtom; +@property (readonly) BOOL isKeyMatch; + +/*! + @brief Initialize a new key-matching object. + @param k a key to match + @result A key-matching object. + */ +- (id)initWithKeyMatching:(NSString*)k; ++ (id)keyMatching:(NSString*)k; + +/*! + @brief Initialize a new atomic property. + @param n the atom's name + @result An atom. + */ +- (id)initWithAtomName:(NSString*)n; ++ (id)atom:(NSString*)n; + +/*! + @brief Initialize a new property. + @param v the property's value + @param k the associated key + @result A property. + */ +- (id)initWithPropertyValue:(NSString*)v forKey:(NSString*)k; ++ (id)property:(NSString*)k withValue:(NSString*)v; + +/*! + @brief A matching function for properties. + @details Two properties match iff their keys match and one of the following: + (a) they are both atomic, (b) one is a key-matching and one is a non-atomic + property, or (c) they are both non-atomic and their values match. + @param object another GraphElementProperty + @result A boolean. + */ +- (BOOL)matches:(GraphElementProperty*)object; + +/*! + @brief An alias for <tt>matches:</tt>. This allows one to use built-in methods that + filter on <tt>isEqual:</tt> for <tt>NSObject</tt>s. + @param object another GraphElementProperty + @result A boolean. + */ +- (BOOL)isEqual:(id)object; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/GraphElementProperty.m b/tikzit-1/src/common/GraphElementProperty.m new file mode 100644 index 0000000..25e1b15 --- /dev/null +++ b/tikzit-1/src/common/GraphElementProperty.m @@ -0,0 +1,164 @@ +// +// +// GraphElementProperty.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "GraphElementProperty.h" +#import "NSString+Tikz.h" + +@implementation GraphElementProperty + ++ (id)atom:(NSString*)n { +#if __has_feature(objc_arc) + return [[self alloc] initWithAtomName:n]; +#else + return [[[self alloc] initWithAtomName:n] autorelease]; +#endif +} ++ (id)property:(NSString*)k withValue:(NSString*)v { +#if __has_feature(objc_arc) + return [[self alloc] initWithPropertyValue:v forKey:k]; +#else + return [[[self alloc] initWithPropertyValue:v forKey:k] autorelease]; +#endif +} ++ (id)keyMatching:(NSString*)k { +#if __has_feature(objc_arc) + return [[self alloc] initWithKeyMatching:k]; +#else + return [[[self alloc] initWithKeyMatching:k] autorelease]; +#endif +} + +- (id)initWithAtomName:(NSString*)n { + self = [super init]; + if (self) { + [self setKey:n]; + isAtom = YES; + } + return self; +} + +- (id)initWithPropertyValue:(NSString*)v forKey:(NSString*)k { + self = [super init]; + if (self) { + [self setKey:k]; + [self setValue:v]; + } + return self; +} + +- (id)initWithKeyMatching:(NSString*)k { + self = [super init]; + if (self) { + [self setKey:k]; + isKeyMatch = YES; + } + return self; +} + +- (void) dealloc { +#if ! __has_feature(objc_arc) + [key release]; + [value release]; + [super dealloc]; +#endif +} + +- (void)setValue:(NSString *)v { + if (value != v) { +#if ! __has_feature(objc_arc) + [value release]; +#endif + value = [v copy]; + } +} + +- (NSString*)value { + if (isAtom) { + return @"(atom)"; + } else { + return value; + } +} + + +- (void)setKey:(NSString *)k { + if (key != k) { +#if ! __has_feature(objc_arc) + [key release]; +#endif + key = [k copy]; + } + if (key == nil) + key = @""; // don't allow nil keys +} + +- (NSString*)key { + return key; +} + +- (BOOL)isAtom { return isAtom; } +- (BOOL)isKeyMatch { return isKeyMatch; } + +- (BOOL)matches:(GraphElementProperty*)object { + // properties and atoms are taken to be incomparable + if ([self isAtom] != [object isAtom]) return NO; + + // only compare keys if (a) we are both atoms, (b) i am a key match, or (c) object is a key match + if (([self isAtom] && [object isAtom]) || [self isKeyMatch] || [object isKeyMatch]) { + return [[self key] isEqual:[object key]]; + } + + // otherwise compare key and value + return [[self key] isEqual:[object key]] && [[self value] isEqual:[object value]]; +} + +- (BOOL)isEqual:(id)object { + return [self matches:object]; +} + +- (id)copyWithZone:(NSZone*)zone { + if (isAtom) { + return [[GraphElementProperty allocWithZone:zone] initWithAtomName:[self key]]; + } else if (isKeyMatch) { + return [[GraphElementProperty allocWithZone:zone] initWithKeyMatching:[self key]]; + } else { + return [[GraphElementProperty allocWithZone:zone] initWithPropertyValue:[self value] forKey:[self key]]; + } +} + +- (NSString*)description { + if ([self isAtom]) { + return [[self key] tikzEscapedString]; + } else if ([self isKeyMatch]) { + return [NSString stringWithFormat:@"%@=*", [[self key] tikzEscapedString]]; + } else { + return [NSString stringWithFormat:@"%@=%@", + [[self key] tikzEscapedString], + [[self value] tikzEscapedString]]; + } +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/Grid.h b/tikzit-1/src/common/Grid.h new file mode 100644 index 0000000..b267536 --- /dev/null +++ b/tikzit-1/src/common/Grid.h @@ -0,0 +1,110 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> +#import "RenderContext.h" +#import "Transformer.h" + +/*! + * Provides a grid, which can be use for snapping points + * + * The grid is divided into cells, and each cell is further subdivided. + * These subdivisions are the snap points for the grid. + */ +@interface Grid: NSObject <NSCopying> { + Transformer *transformer; + float spacing; + int cellSubdivisions; +} + +/*! + * The number of times to subdivide the edge of each cell + * + * Each cell will be divided into cellSubdivisions^2 squares. + */ +@property (assign) int cellSubdivisions; + +/*! + * The cell spacing + * + * Each cell will be @p cellSpacing wide and @p cellSpacing high. + */ +@property (assign) float cellSpacing; + + +/*! + * Create a new grid object. + * + * @param sp the cell spacing - this will be the width and height of each cell + * @param subs the number of cell subdivisions; the cell will end up being + * divided into subs*subs squares that are each sp/subs wide and high + * @param t the transformer to be used when snapping screen points + */ ++ (Grid*) gridWithSpacing:(float)sp subdivisions:(int)subs transformer:(Transformer*)t; +/*! + * Initialize a grid object. + * + * @param sp the cell spacing - this will be the width and height of each cell + * @param subs the number of cell subdivisions; each cell will end up being + * divided into subs*subs squares that are each sp/subs wide and high + * @param t the transformer to be used when snapping screen points + */ +- (id) initWithSpacing:(float)sp subdivisions:(int)subs transformer:(Transformer*)t; + +/*! + * Snap a point in screen co-ordinates + * + * @param p the point to snap, in screen co-ordinates + * @result @p p aligned to the nearest corner of a cell subdivision + */ +- (NSPoint) snapScreenPoint:(NSPoint)p; +/*! + * Snap a point in base co-ordinates + * + * @param p the point to snap + * @result @p p aligned to the nearest corner of a cell subdivision + */ +- (NSPoint) snapPoint:(NSPoint)p; + +/** + * Renders the grid + * + * The grid is rendered across the entire surface (subject to the context's + * clip). + * + * The internal transformer is used to convert between graph co-ordinates + * and graphics co-ordinates. + * + * @param cr the context to render in + */ +- (void) renderGridInContext:(id<RenderContext>)cr; + +/** + * Renders the grid + * + * The grid is rendered across the entire surface (subject to the context's + * clip). + * + * @param cr the context to render in + * @param t a transformer that will be used to map graph co-ordinates + * to graphics co-ordinates + */ +- (void) renderGridInContext:(id<RenderContext>)cr transformer:(Transformer*)t; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Grid.m b/tikzit-1/src/common/Grid.m new file mode 100644 index 0000000..f597a4a --- /dev/null +++ b/tikzit-1/src/common/Grid.m @@ -0,0 +1,186 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * Copyright 2010 Chris Heunen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "Grid.h" +#import "util.h" + +@implementation Grid + ++ (Grid*) gridWithSpacing:(float)sp subdivisions:(int)subs transformer:(Transformer*)t { + return [[[self alloc] initWithSpacing:sp subdivisions:subs transformer:t] autorelease]; +} + +- (id) initWithSpacing:(float)sp subdivisions:(int)subs transformer:(Transformer*)t { + self = [super init]; + + if (self) { + transformer = [t retain]; + spacing = sp; + cellSubdivisions = subs; + } + + return self; +} + +- (id) copyWithZone:(NSZone*)zone { + return [[Grid allocWithZone:zone] + initWithSpacing:spacing + subdivisions:cellSubdivisions + transformer:transformer]; +} + +- (void) dealloc { + [transformer release]; + [super dealloc]; +} + +- (int) cellSubdivisions { + return cellSubdivisions; +} + +- (void) setCellSubdivisions:(int)count { + cellSubdivisions = count; +} + +- (float) cellSpacing { + return spacing; +} + +- (void) setCellSpacing:(float)sp { + spacing = sp; +} + +- (NSPoint) snapScreenPoint:(NSPoint)point { + NSPoint gridPoint = [transformer fromScreen:point]; + return [transformer toScreen:[self snapPoint:gridPoint]]; +} + +- (NSPoint) snapPoint:(NSPoint)p { + const float snapDistance = spacing/(float)cellSubdivisions; + p.x = roundToNearest (snapDistance, p.x); + p.y = roundToNearest (snapDistance, p.y); + return p; +} + +- (void) _setupLinesForContext:(id<RenderContext>)context withSpacing:(float)offset omittingEvery:(int)omitEvery origin:(NSPoint)origin { + NSRect clip = [context clipBoundingBox]; + float clipx1 = clip.origin.x; + float clipx2 = clipx1 + clip.size.width; + float clipy1 = clip.origin.y; + float clipy2 = clipy1 + clip.size.height; + + // left of the Y axis, moving outwards + unsigned int count = 1; + float x = origin.x - offset; + while (x >= clipx1) { + if (omitEvery == 0 || (count % omitEvery != 0)) { + [context moveTo:NSMakePoint(x, clipy1)]; + [context lineTo:NSMakePoint(x, clipy2)]; + } + + x -= offset; + ++count; + } + // right of the Y axis, moving outwards + count = 1; + x = origin.x + offset; + while (x <= clipx2) { + if (omitEvery == 0 || (count % omitEvery != 0)) { + [context moveTo:NSMakePoint(x, clipy1)]; + [context lineTo:NSMakePoint(x, clipy2)]; + } + + x += offset; + ++count; + } + + // above the Y axis, moving outwards + count = 1; + float y = origin.y - offset; + while (y >= clipy1) { + if (omitEvery == 0 || (count % omitEvery != 0)) { + [context moveTo:NSMakePoint(clipx1, y)]; + [context lineTo:NSMakePoint(clipx2, y)]; + } + + y -= offset; + ++count; + } + // below the Y axis, moving outwards + count = 1; + y = origin.y + offset; + while (y <= clipy2) { + if (omitEvery == 0 || (count % omitEvery != 0)) { + [context moveTo:NSMakePoint(clipx1, y)]; + [context lineTo:NSMakePoint(clipx2, y)]; + } + + y += offset; + ++count; + } +} + +- (void) _renderSubdivisionsWithContext:(id<RenderContext>)context origin:(NSPoint)origin cellSize:(float)cellSize { + const float offset = cellSize / cellSubdivisions; + + [self _setupLinesForContext:context withSpacing:offset omittingEvery:cellSubdivisions origin:origin]; + + [context strokePathWithColor:MakeSolidRColor (0.9, 0.9, 1.0)]; +} + +- (void) _renderCellsWithContext:(id<RenderContext>)context origin:(NSPoint)origin cellSize:(float)cellSize { + [self _setupLinesForContext:context withSpacing:cellSize omittingEvery:0 origin:origin]; + + [context strokePathWithColor:MakeSolidRColor (0.8, 0.8, 0.9)]; +} + +- (void) _renderAxesWithContext:(id<RenderContext>)context origin:(NSPoint)origin { + NSRect clip = [context clipBoundingBox]; + + [context moveTo:NSMakePoint(origin.x, clip.origin.y)]; + [context lineTo:NSMakePoint(origin.x, clip.origin.y + clip.size.height)]; + [context moveTo:NSMakePoint(clip.origin.x, origin.y)]; + [context lineTo:NSMakePoint(clip.origin.x + clip.size.width, origin.y)]; + + [context strokePathWithColor:MakeSolidRColor (0.6, 0.6, 0.7)]; +} + +- (void) renderGridInContext:(id<RenderContext>)cr { + [self renderGridInContext:cr transformer:transformer]; +} + +- (void) renderGridInContext:(id<RenderContext>)context transformer:(Transformer*)t { + const NSPoint origin = [t toScreen:NSZeroPoint]; + const float cellSize = [t scaleToScreen:spacing]; + + [context saveState]; + + // common line settings + [context setLineWidth:1.0]; + [context setAntialiasMode:AntialiasDisabled]; + + [self _renderSubdivisionsWithContext:context origin:origin cellSize:cellSize]; + [self _renderCellsWithContext:context origin:origin cellSize:cellSize]; + [self _renderAxesWithContext:context origin:origin]; + + [context restoreState]; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSError+Tikzit.h b/tikzit-1/src/common/NSError+Tikzit.h new file mode 100644 index 0000000..0f45fba --- /dev/null +++ b/tikzit-1/src/common/NSError+Tikzit.h @@ -0,0 +1,44 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> + +NSString* const TZErrorDomain; + +enum { + TZ_ERR_OTHER = 1, + TZ_ERR_BADSTATE, + TZ_ERR_BADFORMAT, + TZ_ERR_IO, + TZ_ERR_TOOL_FAILED, + TZ_ERR_NOTDIRECTORY, + TZ_ERR_PARSE +}; + +NSString* const TZToolOutputErrorKey; + +@interface NSError(Tikzit) ++ (NSString*)tikzitErrorDomain; ++ (id) errorWithMessage:(NSString*)message code:(NSInteger)code cause:(NSError*)cause; ++ (id) errorWithMessage:(NSString*)message code:(NSInteger)code; ++ (id) errorWithLibcError:(NSInteger)errnum; +- (NSString*)toolOutput; +@end + +void logError (NSError *error, NSString *message); + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSError+Tikzit.m b/tikzit-1/src/common/NSError+Tikzit.m new file mode 100644 index 0000000..6b9404b --- /dev/null +++ b/tikzit-1/src/common/NSError+Tikzit.m @@ -0,0 +1,64 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "NSError+Tikzit.h" + +NSString* const TZErrorDomain = @"tikzit"; + +NSString* const TZToolOutputErrorKey = @"tool-output"; + +@implementation NSError(Tikzit) ++ (NSString*)tikzitErrorDomain { + return TZErrorDomain; +} + ++ (id) errorWithMessage:(NSString*)message code:(NSInteger)code cause:(NSError*)cause { + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionaryWithCapacity:2]; + [errorDetail setValue:message forKey:NSLocalizedDescriptionKey]; + if (cause) + [errorDetail setValue:cause forKey:NSUnderlyingErrorKey]; + return [self errorWithDomain:TZErrorDomain code:code userInfo:errorDetail]; +} + ++ (id) errorWithMessage:(NSString*)message code:(NSInteger)code { + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionaryWithObject:message + forKey:NSLocalizedDescriptionKey]; + return [self errorWithDomain:TZErrorDomain code:code userInfo:errorDetail]; +} + ++ (id) errorWithLibcError:(NSInteger)errnum { + NSString *message = [NSString stringWithUTF8String:strerror(errnum)]; + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionaryWithObject:message + forKey:NSLocalizedDescriptionKey]; + return [self errorWithDomain:NSPOSIXErrorDomain code:errnum userInfo:errorDetail]; +} + +- (NSString*)toolOutput { + return [[self userInfo] objectForKey:TZToolOutputErrorKey]; +} + +@end + +void logError (NSError *error, NSString *message) { + if (message == nil) { + NSLog (@"%@", [error localizedDescription]); + } else { + NSLog (@"%@: %@", message, [error localizedDescription]); + } +} + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSFileManager+Utils.h b/tikzit-1/src/common/NSFileManager+Utils.h new file mode 100644 index 0000000..1349919 --- /dev/null +++ b/tikzit-1/src/common/NSFileManager+Utils.h @@ -0,0 +1,29 @@ +// +// NSFileManager+Utils.h +// TikZiT +// +// Copyright 2010 Alex Merry +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> + +@interface NSFileManager(Utils) +- (BOOL) ensureDirectoryExists:(NSString*)path error:(NSError**)error; +@end + +// vi:ft=objc:sts=4:sw=4:ts=4:noet diff --git a/tikzit-1/src/common/NSFileManager+Utils.m b/tikzit-1/src/common/NSFileManager+Utils.m new file mode 100644 index 0000000..87ede95 --- /dev/null +++ b/tikzit-1/src/common/NSFileManager+Utils.m @@ -0,0 +1,46 @@ +// +// NSFileManager+Utils.h +// TikZiT +// +// Copyright 2010 Alex Merry +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> +#import "NSError+Tikzit.h" + +@implementation NSFileManager(Utils) +- (BOOL) ensureDirectoryExists:(NSString*)directory error:(NSError**)error { + BOOL isDirectory = NO; + if (![self fileExistsAtPath:directory isDirectory:&isDirectory]) { + if (![self createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:error]) { + return NO; + } + } else if (!isDirectory) { + if (error) { + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionary]; + [errorDetail setValue:@"Directory is a file" forKey:NSLocalizedDescriptionKey]; + [errorDetail setValue:directory forKey:NSFilePathErrorKey]; + *error = [NSError errorWithDomain:TZErrorDomain code:TZ_ERR_NOTDIRECTORY userInfo:errorDetail]; + } + return NO; + } + return YES; +} +@end + +// vi:ft=objc:sts=4:sw=4:ts=4:noet diff --git a/tikzit-1/src/common/NSString+LatexConstants.h b/tikzit-1/src/common/NSString+LatexConstants.h new file mode 100644 index 0000000..f4b5236 --- /dev/null +++ b/tikzit-1/src/common/NSString+LatexConstants.h @@ -0,0 +1,33 @@ +// +// NSString+LatexConstants.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> + + +@interface NSString(LatexConstants) + +- (NSString*)stringByExpandingLatexConstants; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSString+LatexConstants.m b/tikzit-1/src/common/NSString+LatexConstants.m new file mode 100644 index 0000000..634c189 --- /dev/null +++ b/tikzit-1/src/common/NSString+LatexConstants.m @@ -0,0 +1,212 @@ +// +// NSString+LatexConstants.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "NSString+LatexConstants.h" + +// can't use sizeof() in non-fragile ABI (eg: clang) +#define texConstantCount 63 +static NSString *texConstantNames[texConstantCount] = { + @"alpha", + @"beta", + @"gamma", + @"delta", + @"epsilon", + @"zeta", + @"eta", + @"theta", + @"iota", + @"kappa", + @"lambda", + @"mu", + @"nu", + @"xi", + @"pi", + @"rho", + @"sigma", + @"tau", + @"upsilon", + @"phi", + @"chi", + @"psi", + @"omega", + @"Gamma", + @"Delta", + @"Theta", + @"Lambda", + @"Xi", + @"Pi", + @"Sigma", + @"Upsilon", + @"Phi", + @"Psi", + @"Omega", + + @"pm", + @"to", + @"Rightarrow", + @"Leftrightarrow", + @"forall", + @"partial", + @"exists", + @"emptyset", + @"nabla", + @"in", + @"notin", + @"prod", + @"sum", + @"surd", + @"infty", + @"wedge", + @"vee", + @"cap", + @"cup", + @"int", + @"approx", + @"neq", + @"equiv", + @"leq", + @"geq", + @"subset", + @"supset", + @"cdot", + @"ldots" +}; + +static char * texConstantCodes[texConstantCount] = { + "\u03b1","\u03b2","\u03b3","\u03b4","\u03b5","\u03b6","\u03b7", + "\u03b8","\u03b9","\u03ba","\u03bb","\u03bc","\u03bd","\u03be", + "\u03c0","\u03c1","\u03c3","\u03c4","\u03c5","\u03c6","\u03c7", + "\u03c8","\u03c9","\u0393","\u0394","\u0398","\u039b","\u039e", + "\u03a0","\u03a3","\u03a5","\u03a6","\u03a8","\u03a9", + + "\u00b1","\u2192","\u21d2","\u21d4","\u2200","\u2202","\u2203", + "\u2205","\u2207","\u2208","\u2209","\u220f","\u2211","\u221a", + "\u221e","\u2227","\u2228","\u2229","\u222a","\u222b","\u2248", + "\u2260","\u2261","\u2264","\u2265","\u2282","\u2283","\u22c5", + "\u2026" +}; + +#define texModifierCount 10 +static NSString *texModifierNames[texModifierCount] = { + @"tiny", + @"scriptsize", + @"footnotesize", + @"small", + @"normalsize", + @"large", + @"Large", + @"LARGE", + @"huge", + @"Huge" +}; + +static NSDictionary *texConstants = nil; +static NSSet *texModifiers = nil; + +@implementation NSString(LatexConstants) + +- (NSString*)stringByExpandingLatexConstants { + + if (texConstants == nil) { + NSMutableDictionary *constants = [[NSMutableDictionary alloc] initWithCapacity:texConstantCount]; + for (int i = 0; i < texConstantCount; ++i) { + [constants setObject:[NSString stringWithUTF8String:texConstantCodes[i]] forKey:texConstantNames[i]]; + } + texConstants = constants; + } + if (texModifiers == nil) { + texModifiers = [[NSSet alloc] initWithObjects:texModifierNames count:texModifierCount]; + } + + NSMutableString *buf = [[NSMutableString alloc] initWithCapacity:[self length]]; + NSMutableString *wordBuf = [[NSMutableString alloc] initWithCapacity:10]; + + unichar c_a = [@"a" characterAtIndex:0]; + unichar c_z = [@"z" characterAtIndex:0]; + unichar c_A = [@"A" characterAtIndex:0]; + unichar c_Z = [@"Z" characterAtIndex:0]; + + int state = 0; + // a tiny little DFA to replace \\([\w*]) with unicode of $1 + unichar c; + NSString *code; + int i; + for (i = 0; i<[self length]; ++i) { + c = [self characterAtIndex:i]; + switch (state) { + case 0: + if (c=='\\') { + state = 1; + } else if (c!='$') { + [buf appendFormat:@"%C", c]; + } + break; + case 1: + if ((c>=c_a && c<=c_z) || (c>=c_A && c<=c_Z)) { + [wordBuf appendFormat:@"%C", c]; + } else { + code = [texConstants objectForKey:wordBuf]; + if (code != nil) { + [buf appendString:code]; + } else if (![texModifiers containsObject:wordBuf]) { + [buf appendFormat:@"\\%@", wordBuf]; + } + + [wordBuf setString:@""]; + if (c=='\\') { + state = 1; + } else { + if (c!='$') { + [buf appendFormat:@"%C", c]; + } + state = 0; + } + + } + break; + } + } + + if (state == 1) { + code = [texConstants objectForKey:wordBuf]; + if (code != nil) { + [buf appendString:code]; + } else if (![texModifiers containsObject:wordBuf]) { + [buf appendFormat:@"\\%@", wordBuf]; + } + } + + NSString *ret = [buf copy]; +#if __has_feature(objc_arc) + return ret; +#else + [buf release]; + [wordBuf release]; + + return [ret autorelease]; +#endif +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSString+Tikz.h b/tikzit-1/src/common/NSString+Tikz.h new file mode 100644 index 0000000..ea6ea40 --- /dev/null +++ b/tikzit-1/src/common/NSString+Tikz.h @@ -0,0 +1,26 @@ +/* + * Copyright 2013 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> + +@interface NSString (Tikz) + - (NSString*) tikzEscapedString; + - (BOOL) isValidTikzPropertyNameOrValue; + - (BOOL) isValidAnchor; +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSString+Tikz.m b/tikzit-1/src/common/NSString+Tikz.m new file mode 100644 index 0000000..1e3073b --- /dev/null +++ b/tikzit-1/src/common/NSString+Tikz.m @@ -0,0 +1,72 @@ +/* + * Copyright 2013 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "NSString+Tikz.h" +#import "TikzGraphAssembler.h" + +@implementation NSString (Tikz) + +- (NSString*) tikzEscapedString { + static NSCharacterSet *avoid = nil; + if (avoid == nil) +#if __has_feature(objc_arc) + avoid = [[NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ<>-'0123456789. "] invertedSet]; +#else + avoid = [[[NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ<>-'0123456789. "] invertedSet] retain]; +#endif + + + if ([self rangeOfCharacterFromSet:avoid].length > 0) { + return [NSString stringWithFormat:@"{%@}", self]; + } else { +#if __has_feature(objc_arc) + return self; +#else + return [[self retain] autorelease]; +#endif + } +} + +- (BOOL) isValidTikzPropertyNameOrValue { + NSUInteger length = [self length]; + unsigned int brace_depth = 0; + unsigned int escape = 0; + for (NSUInteger i = 0; i < length; ++i) { + unichar c = [self characterAtIndex:i]; + + if (escape) { + escape = 0; + } else if (c == '\\') { + escape = 1; + } else if (c == '{') { + brace_depth++; + } else if (c == '}') { + if (brace_depth == 0) + return NO; + brace_depth--; + } + } + return !escape && brace_depth == 0; +} + +- (BOOL) isValidAnchor { + return [TikzGraphAssembler validateTikzEdgeAnchor:self]; +} + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSString+Util.h b/tikzit-1/src/common/NSString+Util.h new file mode 100644 index 0000000..548edb3 --- /dev/null +++ b/tikzit-1/src/common/NSString+Util.h @@ -0,0 +1,27 @@ +/* + * Copyright 2013 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> + +@interface NSString (Util) + + (NSString*) stringWithContentsOfFile:(NSString*)path + error:(NSError**)error; + - (id) initWithContentsOfFile:(NSString*)path + error:(NSError**)error; +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/NSString+Util.m b/tikzit-1/src/common/NSString+Util.m new file mode 100644 index 0000000..b18f397 --- /dev/null +++ b/tikzit-1/src/common/NSString+Util.m @@ -0,0 +1,66 @@ +/* + * Copyright 2013 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> +#import "NSString+Util.h" +#import "NSError+Tikzit.h" + +@implementation NSString (Util) ++ (NSString*) stringWithContentsOfFile:(NSString*)path + error:(NSError**)error +{ + return [[[self alloc] initWithContentsOfFile:path error:error] autorelease]; +} +- (id) initWithContentsOfFile:(NSString*)path + error:(NSError**)error +{ + // Fun fact: on GNUstep, at least, + // [stringWithContentsOfFile:usedEncoding:error:] only + // sets error objects if the decoding fails, not if file + // access fails. + // Fun fact 2: on GNUstep, trying to read a directory using + // [stringWithContentsOfFile:] causes an out-of-memory error; + // hence we do these checks *before* trying to read the file. + NSFileManager *fm = [NSFileManager defaultManager]; + BOOL isDir = NO; + NSString *msg = nil; + if (![fm fileExistsAtPath:path isDirectory:&isDir]) { + msg = [NSString stringWithFormat:@"\"%@\" does not exist", path]; + } else if (isDir) { + msg = [NSString stringWithFormat:@"\"%@\" is a directory", path]; + } else if (![fm isReadableFileAtPath:path]) { + msg = [NSString stringWithFormat:@"\"%@\" is not readable", path]; + } + if (msg != nil) { + if (error) { + *error = [NSError errorWithMessage:msg + code:TZ_ERR_IO]; + } + return nil; + } + self = [self initWithContentsOfFile:path]; + if (self == nil) { + if (error) { + *error = [NSError errorWithMessage:@"unknown error" + code:TZ_ERR_IO]; + } + } + return self; +} +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Node.h b/tikzit-1/src/common/Node.h new file mode 100644 index 0000000..1e580ce --- /dev/null +++ b/tikzit-1/src/common/Node.h @@ -0,0 +1,181 @@ +// +// Node.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + + +// Node : store the data associated with a node. + +#import <Foundation/Foundation.h> + +#import "NodeStyle.h" +#import "GraphElementData.h" + +@class GraphElementProperty; +@class Shape; +@class Transformer; + +/*! + @class Node + @brief A graph node, with associated location and style data. + */ +@interface Node : NSObject<NSCopying> { + NSPoint point; + NodeStyle *style; + NSString *name; + NSString *label; + GraphElementData *data; +} + +/*! + @property shape + @brief The shape to use + @detail This is a convenience property that resolves the shape name + from the style, and uses a circle if there is no style. + + This property is NOT KVO-compliant + */ +@property (readonly) Shape *shape; + +/*! + @property point + @brief The point where this node is located. + */ +@property (assign) NSPoint point; + +/*! + @property style + @brief The style of this node. + */ +@property (retain) NodeStyle *style; + +/*! + @property name + @brief The name of this node. This is a temporary name and may change between + successive TikZ outputs. + */ +@property (copy) NSString *name; + +/*! + @property label + @brief The latex label that appears on this node. + */ +@property (copy) NSString *label; + +/*! + @property data + @brief Associated extra data. + */ +@property (copy) GraphElementData *data; + +// KVC methods +- (void) insertObject:(GraphElementProperty*)gep + inDataAtIndex:(NSUInteger)index; +- (void) removeObjectFromDataAtIndex:(NSUInteger)index; +- (void) replaceObjectInDataAtIndex:(NSUInteger)index + withObject:(GraphElementProperty*)gep; + +/*! + @brief Initialize a new node with the given point. + @param p a point. + @result A node. + */ +- (id)initWithPoint:(NSPoint)p; + +/*! + @brief Initialize a new node at (0,0). + @result A node. + */ +- (id)init; + +/*! + @brief Composes the shape transformer with another transformer + @param t The transform to apply before the shape transform + @result A transformer that first maps according to t, then according + to -shapeTransformer. + */ +- (Transformer*) shapeTransformerFromTransformer:(Transformer*)t; + +/*! + @brief A transformer that may be used to convert the shape to the + right position and scale + */ +- (Transformer*) shapeTransformer; + +/*! + @brief The bounding rect in the given co-ordinate system + @detail This is the bounding rect of the shape (after being + suitably translated and scaled). The label is not + considered. + @param shapeTrans The mapping from graph co-ordinates to the required + co-ordinates + @result The bounding rectangle + */ +- (NSRect) boundsUsingShapeTransform:(Transformer*)shapeTrans; + +/*! + @brief The bounding rect in graph co-ordinates + @detail This is the bounding rect of the shape (after being suitably + translated and scaled). The label is not considered. + */ +- (NSRect) boundingRect; + +/*! + @brief Try to attach a style of the correct name from the given style list. + @param styles an array of styles. + @result YES if successfully attached, NO otherwise. + */ +- (BOOL)attachStyleFromTable:(NSArray*)styles; + +/*! + @brief Set node properties from <tt>GraphElementData</tt>. + */ +- (void)updateData; + +/*! + @brief Set properties of this node to match the given node. + @param nd a node to mimic. + */ +- (void)setPropertiesFromNode:(Node *)nd; + +/*! + @brief Compare a node to another node using a lex ordering on coordinates. + @param nd another node. + @result A comparison result. + */ +- (NSComparisonResult)compareTo:(id)nd; + +/*! + @brief Factory method to construct a node with the given point. + @param p a point. + @result A node. + */ ++ (Node*)nodeWithPoint:(NSPoint)p; + +/*! + @brief Factory method to construct a node at (0,0). + @result A node. + */ ++ (Node*)node; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Node.m b/tikzit-1/src/common/Node.m new file mode 100644 index 0000000..c5b11d1 --- /dev/null +++ b/tikzit-1/src/common/Node.m @@ -0,0 +1,214 @@ +// +// Node.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "Node.h" + +#import "Shape.h" + + +@implementation Node + +- (id)initWithPoint:(NSPoint)p { + self = [super init]; + if (self) { + data = [[GraphElementData alloc] init]; + style = nil; + label = @""; + point = p; + } + return self; +} + +- (id)init { + return [self initWithPoint:NSMakePoint(0.0f, 0.0f)]; +} + +- (id)copyWithZone:(NSZone*)z { + Node *cp = [[Node allocWithZone:z] init]; + [cp setPropertiesFromNode:self]; + return cp; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [name release]; + [style release]; + [data release]; + [label release]; + [super dealloc]; +#endif +} + +- (Shape*) shape { + if (style) { + return [Shape shapeForName:[style shapeName]]; + } else { + return nil; + } +} + +- (Transformer*) shapeTransformerFromTransformer:(Transformer*)t { + // we take a copy to keep the reflection attributes +#if ! __has_feature(objc_arc) + Transformer *transformer = [[t copy] autorelease]; +#else + Transformer *transformer = [t copy]; +#endif + NSPoint screenPos = [t toScreen:point]; + [transformer setOrigin:screenPos]; + float scale = [t scale]; + if (style) { + scale *= [style scale]; + } + [transformer setScale:scale]; + return transformer; +} + +- (Transformer*) shapeTransformer { + float scale = 1.0f; + if (style) { + scale = [style scale]; + } + return [Transformer transformerWithOrigin:point andScale:scale]; +} + +- (NSRect) boundsUsingShapeTransform:(Transformer*)shapeTrans { + //if (style) { + return [shapeTrans rectToScreen:[[self shape] boundingRect]]; + /*} else { + NSRect r = NSZeroRect; + r.origin = [shapeTrans toScreen:[self point]]; + return r; + }*/ +} + +- (NSRect) boundingRect { + return [self boundsUsingShapeTransform:[self shapeTransformer]]; +} + +- (BOOL)attachStyleFromTable:(NSArray*)styles { +#if __has_feature(objc_arc) + NSString *style_name = [data propertyForKey:@"style"]; +#else + NSString *style_name = [[[data propertyForKey:@"style"] retain] autorelease]; +#endif + + [self setStyle:nil]; + + // 'none' is a reserved style + if (style_name == nil || [style_name isEqualToString:@"none"]) return YES; + + for (NodeStyle *s in styles) { + if ([[s name] compare:style_name]==NSOrderedSame) { + [self setStyle:s]; + return YES; + } + } + + // if we didn't find a style, fill in a default one + [self setStyle:[NodeStyle defaultNodeStyleWithName:style_name]]; + return NO; +} + +- (void)updateData { + if (style == nil) { + [data setProperty:@"none" forKey:@"style"]; + } else { + [data setProperty:[style name] forKey:@"style"]; + } +} + +- (void)setPropertiesFromNode:(Node*)nd { + [self setPoint:[nd point]]; + [self setStyle:[nd style]]; + [self setName:[nd name]]; + [self setData:[nd data]]; + [self setLabel:[nd label]]; +} + ++ (Node*)nodeWithPoint:(NSPoint)p { +#if __has_feature(objc_arc) + return [[Node alloc] initWithPoint:p]; +#else + return [[[Node alloc] initWithPoint:p] autorelease]; +#endif +} + ++ (Node*)node { +#if __has_feature(objc_arc) + return [[Node alloc] init]; +#else + return [[[Node alloc] init] autorelease]; +#endif +} + + +// perform a lexicographic ordering (-y, x) on coordinates. +- (NSComparisonResult)compareTo:(id)nd { + Node *node = (Node*)nd; + if (point.y > [node point].y) return NSOrderedAscending; + else if (point.y < [node point].y) return NSOrderedDescending; + else { + if (point.x < [node point].x) return NSOrderedAscending; + else if (point.x > [node point].x) return NSOrderedDescending; + else return NSOrderedSame; + } +} + +@synthesize name; +@synthesize label; +@synthesize point; + +@synthesize data; +- (void) insertObject:(GraphElementProperty*)gep + inDataAtIndex:(NSUInteger)index { + [data insertObject:gep atIndex:index]; +} +- (void) removeObjectFromDataAtIndex:(NSUInteger)index { + [data removeObjectAtIndex:index]; +} +- (void) replaceObjectInDataAtIndex:(NSUInteger)index + withObject:(GraphElementProperty*)gep { + [data replaceObjectAtIndex:index withObject:gep]; +} + +- (NodeStyle*)style { + return style; +} + +- (void)setStyle:(NodeStyle *)st { + if (style != st) { +#if __has_feature(objc_arc) + style = st; +#else + NodeStyle *oldStyle = style; + style = [st retain]; + [oldStyle release]; +#endif + } + [self updateData]; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/NodeStyle.h b/tikzit-1/src/common/NodeStyle.h new file mode 100644 index 0000000..034f95d --- /dev/null +++ b/tikzit-1/src/common/NodeStyle.h @@ -0,0 +1,125 @@ +// +// NodeStyle.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "util.h" +#import "ColorRGB.h" +#import "PropertyHolder.h" + +/*! + @class NodeStyle + @brief Store node style information. + @details Store node style information. These properties affect how a node + is displayed in TikZiT. Colors are stored in the ColorRGB struct + to avoid any Cocoa dependency. These styles should be persistant, + which should be implemented in a platform-specific category. For + OS X, this is NodeStyle+Coder. + */ +@interface NodeStyle : PropertyHolder <NSCopying> { + int strokeThickness; + float scale; + ColorRGB *strokeColorRGB; + ColorRGB *fillColorRGB; + NSString *name; + NSString *shapeName; + NSString *category; +} + +/*! + @property strokeThickness + @brief Thickness of the stroke. + */ +@property (assign) int strokeThickness; + +/*! + @property scale + @brief Overall scale of the shape. Defaults to 1.0. + */ +@property (assign) float scale; + + +/*! + @property strokeColorRGB + @brief The stroke color used to render the node + */ +@property (copy) ColorRGB *strokeColorRGB; + +/*! + @property fillColorRGB + @brief The fill color used to render the node + */ +@property (copy) ColorRGB *fillColorRGB; + +/*! + @property name + @brief Style name. + @details Style name. This is the only thing that affects how the node + will look when the latex code is rendered. + */ +@property (copy) NSString *name; + +/*! + @property shapeName + @brief The name of the shape that will be drawn in TikZiT. + */ +@property (copy) NSString *shapeName; + +/*! + @property category + @brief ??? + */ +@property (copy) NSString *category; + +@property (readonly) NSString *tikz; +@property (readonly) BOOL strokeColorIsKnown; +@property (readonly) BOOL fillColorIsKnown; + ++ (int) defaultStrokeThickness; + +/*! + @brief Designated initializer. Construct a blank style with name 'new'. + @result A default style. + */ +- (id)init; + +/*! + @brief Create a named style. + @param nm the style name. + @result A <tt>NodeStyle</tt> with the given name. + */ +- (id)initWithName:(NSString *)nm; + +/*! + @brief Factory method for initWithName: + @param nm the style name. + @result A <tt>NodeStyle</tt> with the given name. + */ ++ (NodeStyle*)defaultNodeStyleWithName:(NSString *)nm; + +/*! + * Make this style the same as the given one + */ +- (void) updateFromStyle:(NodeStyle*)style; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/NodeStyle.m b/tikzit-1/src/common/NodeStyle.m new file mode 100644 index 0000000..193d44d --- /dev/null +++ b/tikzit-1/src/common/NodeStyle.m @@ -0,0 +1,246 @@ +// +// NodeStyle.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "NodeStyle.h" +#import "Shape.h" +#import "ShapeNames.h" + +@implementation NodeStyle + ++ (void)initialize { + [self setKeys:[NSArray arrayWithObjects: + @"fillColorRGB.red", + @"fillColorRGB.blue", + @"fillColorRGB.green", + @"strokeColorRGB.red", + @"strokeColorRGB.blue", + @"strokeColorRGB.green", + @"strokeThickness", + @"shapeName", + @"name", + nil] + triggerChangeNotificationsForDependentKey:@"tikz"]; + [self setKeys:[NSArray arrayWithObjects: + @"fillColorRGB.name", + nil] + triggerChangeNotificationsForDependentKey:@"fillColorIsKnown"]; + [self setKeys:[NSArray arrayWithObjects: + @"strokeColorRGB.name", + nil] + triggerChangeNotificationsForDependentKey:@"strokeColorIsKnown"]; +} + ++ (int) defaultStrokeThickness { return 1; } + +- (id)initWithName:(NSString*)nm { + self = [super initWithNotificationName:@"NodeStylePropertyChanged"]; + if (self != nil) { + strokeThickness = [NodeStyle defaultStrokeThickness]; + scale = 1.0f; + strokeColorRGB = [[ColorRGB alloc] initWithRed:0 green:0 blue:0]; + fillColorRGB = [[ColorRGB alloc] initWithRed:255 green:255 blue:255]; + + name = nm; + category = nil; + shapeName = SHAPE_CIRCLE; + } + return self; +} + +- (id)init { + self = [self initWithName:@"new"]; + return self; +} + +- (id)copyWithZone:(NSZone*)zone { + NodeStyle *style = [[NodeStyle allocWithZone:zone] init]; + + [style setStrokeThickness:[self strokeThickness]]; + [style setScale:[self scale]]; + [style setStrokeColorRGB:[self strokeColorRGB]]; + [style setFillColorRGB:[self fillColorRGB]]; + [style setName:[self name]]; + [style setShapeName:[self shapeName]]; + [style setCategory:[self category]]; + + return style; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [name release]; + [category release]; + [shapeName release]; + [strokeColorRGB release]; + [fillColorRGB release]; + [super dealloc]; +#endif +} + +- (NSString*) description { + return [NSString stringWithFormat:@"Node style \"%@\"", name]; +} + +- (void) updateFromStyle:(NodeStyle*)style { + [self setStrokeThickness:[style strokeThickness]]; + [self setScale:[style scale]]; + [self setStrokeColorRGB:[style strokeColorRGB]]; + [self setFillColorRGB:[style fillColorRGB]]; + [self setName:[style name]]; + [self setShapeName:[style shapeName]]; + [self setCategory:[style category]]; +} + ++ (NodeStyle*)defaultNodeStyleWithName:(NSString*)nm { +#if __has_feature(objc_arc) + return [[NodeStyle alloc] initWithName:nm]; +#else + return [[[NodeStyle alloc] initWithName:nm] autorelease]; +#endif +} + +- (NSString*)name { + return name; +} + +- (void)setName:(NSString *)s { + if (name != s) { + NSString *oldValue = name; + name = [s copy]; + [self postPropertyChanged:@"name" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (NSString*)shapeName { + return shapeName; +} + +- (void)setShapeName:(NSString *)s { + if (shapeName != s) { + NSString *oldValue = shapeName; + shapeName = [s copy]; + [self postPropertyChanged:@"shapeName" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (NSString*)category { + return category; +} + +- (void)setCategory:(NSString *)s { + if (category != s) { + NSString *oldValue = category; + category = [s copy]; + [self postPropertyChanged:@"category" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (int)strokeThickness { return strokeThickness; } +- (void)setStrokeThickness:(int)i { + int oldValue = strokeThickness; + strokeThickness = i; + [self postPropertyChanged:@"strokeThickness" oldValue:[NSNumber numberWithInt:oldValue]]; +} + +- (float)scale { return scale; } +- (void)setScale:(float)s { + float oldValue = scale; + scale = s; + [self postPropertyChanged:@"scale" oldValue:[NSNumber numberWithFloat:oldValue]]; +} + +- (ColorRGB*)strokeColorRGB { + return strokeColorRGB; +} + +- (void)setStrokeColorRGB:(ColorRGB*)c { + if (strokeColorRGB != c) { + ColorRGB *oldValue = strokeColorRGB; + strokeColorRGB = [c copy]; + [self postPropertyChanged:@"strokeColorRGB" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (ColorRGB*)fillColorRGB { + return fillColorRGB; +} + +- (void)setFillColorRGB:(ColorRGB*)c { + if (fillColorRGB != c) { + ColorRGB *oldValue = fillColorRGB; + fillColorRGB = [c copy]; + [self postPropertyChanged:@"fillColorRGB" oldValue:oldValue]; +#if ! __has_feature(objc_arc) + [oldValue release]; +#endif + } +} + +- (NSString*)tikz { + NSString *fillName = [fillColorRGB name]; + NSString *strokeName = [strokeColorRGB name]; + NSString *stroke = @""; + if (strokeThickness != 1) { + stroke = [NSString stringWithFormat:@",line width=%@ pt", + [NSNumber numberWithFloat:(float)strokeThickness * 0.4f]]; + } + + // If the colors are unknown, fall back on hexnames. These should be defined as colors + // in the Preambles class. + if (fillName == nil) fillName = [fillColorRGB hexName]; + if (strokeName == nil) strokeName = [strokeColorRGB hexName]; + + NSString *shapeDesc = [[Shape shapeForName:shapeName] styleTikz]; + if (shapeDesc == nil) shapeDesc = shapeName; + + return [NSString stringWithFormat:@"\\tikzstyle{%@}=[%@,fill=%@,draw=%@%@]", + name, + shapeDesc, + fillName, + strokeName, + stroke]; +} + +- (BOOL)strokeColorIsKnown { + return ([strokeColorRGB name] != nil); +} + +- (BOOL)fillColorIsKnown { + return ([fillColorRGB name] != nil); +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/PickSupport.h b/tikzit-1/src/common/PickSupport.h new file mode 100644 index 0000000..0749649 --- /dev/null +++ b/tikzit-1/src/common/PickSupport.h @@ -0,0 +1,164 @@ +// +// PickSupport.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + + +#import <Foundation/Foundation.h> +#import "Node.h" +#import "Edge.h" + +/*! + @class PickSupport + @brief Maintain the selection state of nodes and edges. + @detail In addition to the notifications listed for specific methods, + whenever the node selection changes, a "NodeSelectionChanged" + signal is emitted, and whenever the edge selection changes, + an "EdgeSelectionChanged" signal is emitted. + */ +@interface PickSupport : NSObject { + NSMutableSet *selectedNodes; + NSMutableSet *selectedEdges; +} + +/*! + @property selectedNodes + @brief A set of selected nodes. + */ +@property (readonly) NSSet *selectedNodes; + +// KVC methods +- (void)addSelectedNodesObject:(Node*)node; +- (void)addSelectedNodes:(NSSet*)nodes; +- (void)removeSelectedNodesObject:(Node*)node; +- (void)removeSelectedNodes:(NSSet*)nodes; + +/*! + @property selectedEdges + @brief A set of selected edges. + */ +@property (readonly) NSSet *selectedEdges; + +// KVC methods +- (void)addSelectedEdgesObject:(Edge*)edge; +- (void)addSelectedEdges:(NSSet*)edges; +- (void)removeSelectedEdgesObject:(Edge*)edge; +- (void)removeSelectedEdges:(NSSet*)edges; + +/*! + @brief Check if a node is selected. + @param nd a node. + @result YES if nd is selected. + */ +- (BOOL)isNodeSelected:(Node*)nd; + +/*! + @brief Check if an edge is selected. + @param e an edge. + @result YES if e is selected. + */ +- (BOOL)isEdgeSelected:(Edge*)e; + +/*! + @brief Select a node. + @details Sends the "NodeSelected" notification if the node was not + already selected, with @p nd as "node" in the userInfo + @param nd a node. + */ +- (void)selectNode:(Node*)nd; + +/*! + @brief Deselect a node. + @details Sends the "NodeDeselected" notification if the node was + selected, with @p nd as "node" in the userInfo + @param nd a node. + */ +- (void)deselectNode:(Node*)nd; + +/*! + @brief Select an edge. + @details Sends the "EdgeSelected" notification if the node was not + already selected, with @p e as "edge" in the userInfo + @param e an edge. + */ +- (void)selectEdge:(Edge*)e; + +/*! + @brief Deselect an edge. + @details Sends the "EdgeDeselected" notification if the node was + selected, with @p e as "edge" in the userInfo + @param e an edge. + */ +- (void)deselectEdge:(Edge*)e; + +/*! + @brief Toggle the selected state of the given node. + @details Sends the "NodeSelected" or "NodeDeselected" notification as + appropriate, with @p nd as "node" in the userInfo + @param nd a node. + */ +- (void)toggleNodeSelected:(Node*)nd; + +/*! + @brief Select all nodes in the given set. + @details Sends the "NodeSelectionReplaced" notification if this + caused the selection to change. + + Equivalent to selectAllNodes:nodes replacingSelection:YES + @param nodes a set of nodes. + */ +- (void)selectAllNodes:(NSSet*)nodes; + +/*! + @brief Select all nodes in the given set. + @details Sends the "NodeSelectionReplaced" notification if this + caused the selection to change. + + If replace is NO, @p nodes will be added to the existing + selection, otherwise it will replace the existing selection. + @param nodes a set of nodes. + @param replace whether to replace the existing selection + */ +- (void)selectAllNodes:(NSSet*)nodes replacingSelection:(BOOL)replace; + +/*! + @brief Deselect all nodes. + @details Sends the "NodeSelectionReplaced" notification if there + were any nodes previously selected + */ +- (void)deselectAllNodes; + +/*! + @brief Deselect all edges. + @details Sends the "EdgeSelectionReplaced" notification if there + were any edges previously selected + */ +- (void)deselectAllEdges; + +/*! + @brief Factory method for getting a new <tt>PickSupport</tt> object. + @result An empty <tt>PickSupport</tt>. + */ ++ (PickSupport*)pickSupport; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/PickSupport.m b/tikzit-1/src/common/PickSupport.m new file mode 100644 index 0000000..560fc2c --- /dev/null +++ b/tikzit-1/src/common/PickSupport.m @@ -0,0 +1,232 @@ +// +// PickSupport.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "PickSupport.h" + + +@implementation PickSupport + +- (void) postNodeSelectionChanged { + [[NSNotificationCenter defaultCenter] + postNotificationName:@"NodeSelectionChanged" + object:self]; +} + +- (void) postEdgeSelectionChanged { + [[NSNotificationCenter defaultCenter] + postNotificationName:@"EdgeSelectionChanged" + object:self]; +} + +- (id) init { + self = [super init]; + + if (self) { +#if __has_feature(objc_arc) + selectedNodes = [NSMutableSet set]; + selectedEdges = [NSMutableSet set]; +#else + selectedNodes = [[NSMutableSet set] retain]; + selectedEdges = [[NSMutableSet set] retain]; +#endif + } + + return self; +} + ++ (PickSupport*)pickSupport { +#if __has_feature(objc_arc) + return [[PickSupport alloc] init]; +#else + return [[[PickSupport alloc] init] autorelease]; +#endif +} + +@synthesize selectedNodes; +- (void)addSelectedNodesObject:(Node*)node { + return [self selectNode:node]; +} +- (void)addSelectedNodes:(NSSet*)nodes { + return [self selectAllNodes:nodes replacingSelection:NO]; +} +- (void)removeSelectedNodesObject:(Node*)node { + return [self deselectNode:node]; +} +- (void)removeSelectedNodes:(NSSet*)nodes { + if ([selectedNodes count] > 0) { + [selectedNodes minusSet:nodes]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"NodeSelectionReplaced" + object:self]; + [self postNodeSelectionChanged]; + } +} + +@synthesize selectedEdges; +- (void)addSelectedEdgesObject:(Edge*)edge { + return [self selectEdge:edge]; +} +- (void)addSelectedEdges:(NSSet*)edges { + if (selectedEdges == edges) { + return; + } + if ([edges count] == 0) { + return; + } + + [selectedEdges unionSet:edges]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"EdgeSelectionReplaced" + object:self]; + [self postEdgeSelectionChanged]; +} +- (void)removeSelectedEdgesObject:(Edge*)edge { + return [self deselectEdge:edge]; +} +- (void)removeSelectedEdges:(NSSet*)edges { + if ([selectedEdges count] > 0 && [edges count] > 0) { + [selectedEdges minusSet:edges]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"EdgeSelectionReplaced" + object:self]; + [self postEdgeSelectionChanged]; + } +} + +- (BOOL)isNodeSelected:(Node*)nd { + return [selectedNodes containsObject:nd]; +} + +- (BOOL)isEdgeSelected:(Edge*)e { + return [selectedEdges containsObject:e]; +} + +- (void)selectNode:(Node*)nd { + if (nd != nil && ![selectedNodes member:nd]) { + [selectedNodes addObject:nd]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"NodeSelected" + object:self + userInfo:[NSDictionary dictionaryWithObject:nd forKey:@"node"]]; + [self postNodeSelectionChanged]; + } +} + +- (void)deselectNode:(Node*)nd { + if (nd != nil && [selectedNodes member:nd]) { + [selectedNodes removeObject:nd]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"NodeDeselected" + object:self + userInfo:[NSDictionary dictionaryWithObject:nd forKey:@"node"]]; + [self postNodeSelectionChanged]; + } +} + +- (void)selectEdge:(Edge*)e { + if (e != nil && ![selectedEdges member:e]) { + [selectedEdges addObject:e]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"EdgeSelected" + object:self + userInfo:[NSDictionary dictionaryWithObject:e forKey:@"edge"]]; + [self postEdgeSelectionChanged]; + } +} + +- (void)deselectEdge:(Edge*)e { + if (e != nil && [selectedEdges member:e]) { + [selectedEdges removeObject:e]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"EdgeDeselected" + object:self + userInfo:[NSDictionary dictionaryWithObject:e forKey:@"edge"]]; + [self postEdgeSelectionChanged]; + } +} + +- (void)toggleNodeSelected:(Node*)nd { + if ([self isNodeSelected:nd]) + [self deselectNode:nd]; + else + [self selectNode:nd]; +} + +- (void)selectAllNodes:(NSSet*)nodes { + [self selectAllNodes:nodes replacingSelection:YES]; +} + +- (void)selectAllNodes:(NSSet*)nodes replacingSelection:(BOOL)replace { + if (selectedNodes == nodes) { + return; + } + if (!replace && [nodes count] == 0) { + return; + } + + if (replace) { +#if ! __has_feature(objc_arc) + [selectedNodes release]; +#endif + selectedNodes = [nodes mutableCopy]; + } else { + [selectedNodes unionSet:nodes]; + } + [[NSNotificationCenter defaultCenter] + postNotificationName:@"NodeSelectionReplaced" + object:self]; + [self postNodeSelectionChanged]; +} + +- (void)deselectAllNodes { + if ([selectedNodes count] > 0) { + [selectedNodes removeAllObjects]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"NodeSelectionReplaced" + object:self]; + [self postNodeSelectionChanged]; + } +} + +- (void)deselectAllEdges { + if ([selectedEdges count] > 0) { + [selectedEdges removeAllObjects]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"EdgeSelectionReplaced" + object:self]; + [self postEdgeSelectionChanged]; + } +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [selectedNodes release]; + [selectedEdges release]; + + [super dealloc]; +#endif +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/Preambles.h b/tikzit-1/src/common/Preambles.h new file mode 100644 index 0000000..2fb084a --- /dev/null +++ b/tikzit-1/src/common/Preambles.h @@ -0,0 +1,73 @@ +// +// Preambles.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// Copyright 2011 Alex Merry. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> +#import "StyleManager.h" + +@class Graph; + +@interface Preambles : NSObject { + NSMutableDictionary *preambleDict; + NSString *selectedPreambleName; + NSArray *styles; + NSArray *edges; + StyleManager *styleManager; +} + +@property (copy) NSString *selectedPreambleName; +@property (retain) NSString *currentPreamble; +@property (retain) StyleManager *styleManager; +@property (readonly) NSMutableDictionary *preambleDict; + ++ (Preambles*)preambles; +- (id)init; +- (void)setStyles:(NSArray*)sty; +- (void)setEdges:(NSArray*)edg; + +- (NSString*)preambleForName:(NSString*)name; +- (BOOL)setPreamble:(NSString*)content forName:(NSString*)name; + +- (NSString*)addPreamble; +- (NSString*)addPreambleWithNameBase:(NSString*)name; + +- (BOOL)renamePreambleFrom:(NSString*)old to:(NSString*)new; +- (BOOL)removePreamble:(NSString*)name; + +- (NSEnumerator*)customPreambleNameEnumerator; + +- (void)removeAllPreambles; + +- (BOOL)selectedPreambleIsDefault; + +- (NSString*)styleDefinitions; +- (NSString*)defaultPreamble; +- (NSString*)defaultPreambleName; +- (NSString*)currentPostamble; + +- (NSString*)buildDocumentForTikz:(NSString*)tikz; +- (NSString*)buildDocumentForGraph:(Graph*)g; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Preambles.m b/tikzit-1/src/common/Preambles.m new file mode 100644 index 0000000..922fc30 --- /dev/null +++ b/tikzit-1/src/common/Preambles.m @@ -0,0 +1,320 @@ +// +// Preambles.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "Preambles.h" +#import "NodeStyle.h" +#import "EdgeStyle.h" +#import "Graph.h" + +static NSString *DEF_PREAMBLE_START = +@"\\usepackage[svgnames]{xcolor}\n" +@"\\usepackage{tikz}\n" +@"\\usetikzlibrary{decorations.markings}\n" +@"\\usetikzlibrary{shapes.geometric}\n" +@"\n" +@"\\pgfdeclarelayer{edgelayer}\n" +@"\\pgfdeclarelayer{nodelayer}\n" +@"\\pgfsetlayers{edgelayer,nodelayer,main}\n" +@"\n" +@"\\tikzstyle{none}=[inner sep=0pt]\n"; + +static NSString *PREAMBLE_TAIL = +@"\n" +@"\\pagestyle{empty}\n" +@"\\usepackage[graphics,tightpage,active]{preview}\n" +@"\\PreviewEnvironment{tikzpicture}\n" +@"\\newlength{\\imagewidth}\n" +@"\\newlength{\\imagescale}\n" +@"\n" +@"\\begin{document}\n"; + +static NSString *POSTAMBLE = +@"\n" +@"\\end{document}\n"; + +@implementation Preambles + ++ (Preambles*)preambles { +#if __has_feature(objc_arc) + return [[self alloc] init]; +#else + return [[[self alloc] init] autorelease]; +#endif +} + +- (id)init { + self = [super init]; + if (self) { + selectedPreambleName = @"default"; + preambleDict = [[NSMutableDictionary alloc] initWithCapacity:1]; + [preambleDict setObject:[self defaultPreamble] forKey:@"custom"]; + styles = nil; + edges = nil; + styleManager = nil; + } + return self; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [selectedPreambleName release]; + [styles release]; + [styleManager release]; + [super dealloc]; +#endif +} + +- (NSString*)preambleForName:(NSString*)name { + if ([name isEqualToString:@"default"]) + return [self defaultPreamble]; + else + return [preambleDict objectForKey:name]; +} + +- (BOOL)setPreamble:(NSString*)content forName:(NSString*)name { + if ([name isEqualToString:@"default"]) + return NO; + [preambleDict setObject:content forKey:name]; + return YES; +} + +- (void)removeAllPreambles { + [preambleDict removeAllObjects]; +} + +- (NSEnumerator*)customPreambleNameEnumerator { + return [preambleDict keyEnumerator]; +} + +- (void)setStyles:(NSArray*)sty { +#if ! __has_feature(objc_arc) + [sty retain]; + [styles release]; +#endif + styles = sty; +} + +- (void)setEdges:(NSArray*)edg { +#if ! __has_feature(objc_arc) + [edg retain]; + [edges release]; +#endif + edges = edg; +} + +- (NSString*)styleDefinitions { + if (styleManager != nil) { + [self setStyles:[styleManager nodeStyles]]; + } +#if ! __has_feature(objc_arc) + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; +#endif + NSMutableString *buf = [NSMutableString string]; + NSMutableString *colbuf = [NSMutableString string]; + NSMutableSet *colors = [NSMutableSet setWithCapacity:2*[styles count]]; + for (NodeStyle *st in styles) { + [buf appendFormat:@"%@\n", [st tikz]]; + ColorRGB *fill = [st fillColorRGB]; + ColorRGB *stroke = [st strokeColorRGB]; + if ([fill name] == nil && ![colors containsObject:fill]) { + [colors addObject:fill]; + [colbuf appendFormat:@"\\definecolor{%@}{rgb}{%.3f,%.3f,%.3f}\n", + [fill hexName], [fill redFloat], [fill greenFloat], [fill blueFloat]]; + } + + if ([stroke name] == nil && ![colors containsObject:stroke]) { + [colors addObject:stroke]; + [colbuf appendFormat:@"\\definecolor{%@}{rgb}{%.3f,%.3f,%.3f}\n", + [stroke hexName], [stroke redFloat], [stroke greenFloat], [stroke blueFloat]]; + } + } + + if (styleManager != nil) { + [self setEdges:[styleManager edgeStyles]]; + } + + [buf appendString:@"\n"]; + for (EdgeStyle *st in edges) { + [buf appendFormat:@"%@\n", [st tikz]]; + ColorRGB *color = [st colorRGB]; + if (color != nil && [color name] == nil && ![colors containsObject:color]) { + [colors addObject:color]; + [colbuf appendFormat:@"\\definecolor{%@}{rgb}{%.3f,%.3f,%.3f}\n", + [color hexName], [color redFloat], [color greenFloat], [color blueFloat]]; + } + } + + NSString *defs = [[NSString alloc] initWithFormat:@"%@\n%@", colbuf, buf]; + +#if __has_feature(objc_arc) + return defs; +#else + [pool drain]; + return [defs autorelease]; +#endif +} + +- (NSString*)defaultPreamble { + return [NSString stringWithFormat:@"%@%@", + DEF_PREAMBLE_START, [self styleDefinitions]]; +} + +- (BOOL)selectedPreambleIsDefault { + return [selectedPreambleName isEqualToString:@"default"]; +} + +- (NSString*)selectedPreambleName { return selectedPreambleName; } +- (void)setSelectedPreambleName:(NSString *)sel { + if (sel != selectedPreambleName) { +#if ! __has_feature(objc_arc) + [selectedPreambleName release]; +#endif + selectedPreambleName = [sel copy]; + } +} + +- (NSString*)currentPreamble { + NSString *pre = [self preambleForName:selectedPreambleName]; + return (pre == nil) ? [self defaultPreamble] : pre; +} + +- (void)setCurrentPreamble:(NSString*)str { + if (![selectedPreambleName isEqualToString:@"default"]) + [preambleDict setObject:str forKey:selectedPreambleName]; +} + +- (StyleManager*)styleManager { + return styleManager; +} + +- (void)setStyleManager:(StyleManager *)manager { +#if ! __has_feature(objc_arc) + [manager retain]; + [styleManager release]; +#endif + styleManager = manager; +} + +- (NSString*)currentPostamble { + return POSTAMBLE; +} + +- (NSMutableDictionary*)preambleDict { + return preambleDict; +} + +- (NSString*)defaultPreambleName { + return @"default"; +} + +- (NSString*)addPreamble { + return [self addPreambleWithNameBase:@"new preamble"]; +} + +- (NSString*)addPreambleWithNameBase:(NSString*)base { + if ([preambleDict objectForKey:base] == nil) { + [self setPreamble:[self defaultPreamble] forName:base]; + return base; + } + int i = 0; + NSString *tryName = nil; + do { + ++i; + tryName = [NSString stringWithFormat:@"%@ %d", base, i]; + } while ([preambleDict objectForKey:tryName] != nil); + + [self setPreamble:[self defaultPreamble] forName:tryName]; + return tryName; +} + +- (BOOL)renamePreambleFrom:(NSString*)old to:(NSString*)new { + if ([old isEqualToString:@"default"]) + return NO; + if ([new isEqualToString:@"default"]) + return NO; + if ([old isEqualToString:new]) + return YES; + BOOL isSelected = NO; + if ([old isEqualToString:selectedPreambleName]) { + [self setSelectedPreambleName:nil]; + isSelected = YES; + } + NSString *preamble = [preambleDict objectForKey:old]; +#if ! __has_feature(objc_arc) + [preamble retain]; +#endif + [preambleDict removeObjectForKey:old]; + [preambleDict setObject:preamble forKey:new]; +#if ! __has_feature(objc_arc) + [preamble release]; +#endif + if (isSelected) { + [self setSelectedPreambleName:new]; + } + return YES; +} + +- (BOOL)removePreamble:(NSString*)name { + if ([name isEqualToString:@"default"]) + return NO; + // "name" may be held only by being the selected preamble... +#if ! __has_feature(objc_arc) + [name retain]; +#endif + if ([name isEqualToString:selectedPreambleName]) + [self setSelectedPreambleName:nil]; + [preambleDict removeObjectForKey:name]; +#if ! __has_feature(objc_arc) + [name release]; +#endif + return YES; +} + +- (NSString*)buildDocumentForTikz:(NSString*)tikz +{ + NSString *preamble = [self currentPreamble]; + NSString *doc_head = @""; + if (![preamble hasPrefix:@"\\documentclass"]) { + doc_head = @"\\documentclass{article}\n"; + } + NSString *preamble_suffix = @""; + if ([preamble rangeOfString:@"\\begin{document}" + options:NSBackwardsSearch].length == 0) { + preamble_suffix = PREAMBLE_TAIL; + } + return [NSString stringWithFormat:@"%@%@%@%@%@", + doc_head, + [self currentPreamble], + preamble_suffix, + tikz, + POSTAMBLE]; +} + +- (NSString*)buildDocumentForGraph:(Graph*)g +{ + return [self buildDocumentForTikz:[g tikz]]; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/PropertyHolder.h b/tikzit-1/src/common/PropertyHolder.h new file mode 100644 index 0000000..ba1d825 --- /dev/null +++ b/tikzit-1/src/common/PropertyHolder.h @@ -0,0 +1,36 @@ +// +// PropertyHolder.h +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> + +@interface PropertyHolder : NSObject { + NSString *notificationName; +} + +- (id)initWithNotificationName:(NSString*)name; +- (void) postPropertyChanged:(NSString*)property oldValue:(id)value; +- (void) postPropertyChanged:(NSString*)property; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/PropertyHolder.m b/tikzit-1/src/common/PropertyHolder.m new file mode 100644 index 0000000..6aaf125 --- /dev/null +++ b/tikzit-1/src/common/PropertyHolder.m @@ -0,0 +1,74 @@ +// +// PropertyHolder.m +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "PropertyHolder.h" + +@implementation PropertyHolder + + +- (id)init { + self = [super init]; + if (self) { + notificationName = @"UnknownPropertyChanged"; + } + return self; +} + +- (id)initWithNotificationName:(NSString*)n { + self = [super init]; + if (self) { + notificationName = [n copy]; + } + return self; +} + +- (void)postPropertyChanged:(NSString*)property oldValue:(id)value { + NSDictionary *userInfo; + if (value != nil) { + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + property, @"propertyName", + value, @"oldValue", + nil]; + } else { + userInfo = [NSDictionary dictionaryWithObject:property + forKey:@"propertyName"]; + } + [[NSNotificationCenter defaultCenter] postNotificationName:notificationName + object:self + userInfo:userInfo]; +} + +- (void)postPropertyChanged:(NSString*)property { + [self postPropertyChanged:property oldValue:nil]; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [notificationName release]; + [super dealloc]; +#endif +} + +@end + +// vi:ft=objc:ts=4:et:sts=4:sw=4 diff --git a/tikzit-1/src/common/RColor.h b/tikzit-1/src/common/RColor.h new file mode 100644 index 0000000..7f22547 --- /dev/null +++ b/tikzit-1/src/common/RColor.h @@ -0,0 +1,50 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> + +#ifndef CGFloat +#define CGFloat float +#endif + +/** + * A lightweight color structure used by RenderContext + * + * This is mainly to avoid the overhead of ColorRGB when + * rendering things not based on a NodeStyle + * + * All values range from 0.0f to 1.0f. + */ +typedef struct { + CGFloat red; + CGFloat green; + CGFloat blue; + CGFloat alpha; +} +RColor; + +/** Solid white */ +static const RColor WhiteRColor __attribute__((unused)) = {1.0, 1.0, 1.0, 1.0}; +/** Solid black */ +static const RColor BlackRColor __attribute__((unused)) = {0.0, 0.0, 0.0, 1.0}; + +/** Create a color with alpha set to 1.0 */ +RColor MakeSolidRColor (CGFloat red, CGFloat green, CGFloat blue); +/** Create a color */ +RColor MakeRColor (CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha); + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/RColor.m b/tikzit-1/src/common/RColor.m new file mode 100644 index 0000000..49914fe --- /dev/null +++ b/tikzit-1/src/common/RColor.m @@ -0,0 +1,33 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "RColor.h" + +RColor MakeSolidRColor (CGFloat red, CGFloat green, CGFloat blue) { + return MakeRColor (red, green, blue, 1.0); +} + +RColor MakeRColor (CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha) { + RColor color; + color.red = red; + color.green = green; + color.blue = blue; + color.alpha = alpha; + return color; +} + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/RectangleShape.h b/tikzit-1/src/common/RectangleShape.h new file mode 100644 index 0000000..3fa0f31 --- /dev/null +++ b/tikzit-1/src/common/RectangleShape.h @@ -0,0 +1,33 @@ +// +// RectangleShape.h +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> +#import "Shape.h" + + +@interface RectangleShape : Shape { +} + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/RectangleShape.m b/tikzit-1/src/common/RectangleShape.m new file mode 100644 index 0000000..db9c803 --- /dev/null +++ b/tikzit-1/src/common/RectangleShape.m @@ -0,0 +1,57 @@ +// +// RectangleShape.m +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "RectangleShape.h" +#import "Node.h" +#import "Edge.h" + +@implementation RectangleShape + +- (id)init { + self = [super init]; + if (self) { + Node *n0,*n1,*n2,*n3; + float sz = 0.2f; + + n0 = [Node nodeWithPoint:NSMakePoint(-sz, sz)]; + n1 = [Node nodeWithPoint:NSMakePoint( sz, sz)]; + n2 = [Node nodeWithPoint:NSMakePoint( sz,-sz)]; + n3 = [Node nodeWithPoint:NSMakePoint(-sz,-sz)]; + + Edge *e0,*e1,*e2,*e3; + + e0 = [Edge edgeWithSource:n0 andTarget:n1]; + e1 = [Edge edgeWithSource:n1 andTarget:n2]; + e2 = [Edge edgeWithSource:n2 andTarget:n3]; + e3 = [Edge edgeWithSource:n3 andTarget:n0]; + + paths = [[NSSet alloc] initWithObjects:[NSArray arrayWithObjects:e0,e1,e2,e3,nil],nil]; + + styleTikz = @"rectangle"; + } + return self; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/RegularPolyShape.h b/tikzit-1/src/common/RegularPolyShape.h new file mode 100644 index 0000000..1fd8f1e --- /dev/null +++ b/tikzit-1/src/common/RegularPolyShape.h @@ -0,0 +1,50 @@ +// +// RegularPolyShape.h +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> +#import "Shape.h" + +/** + * A regular polygon + * + * Matches the "regular polygon" shape in the shapes.geometric + * PGF/TikZ library. + */ +@interface RegularPolyShape : Shape { +} + +/** + * Initialise a regular polygon + * + * A rotation of 0 will produce a polygon with one + * edge flat along the bottom (just like PGF/TikZ + * does it). + * + * @param sides the number of sides the polygon should have + * @param rotation the rotation of the polygon, in degrees + */ +- (id)initWithSides:(int)sides rotation:(int)rotation; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/RegularPolyShape.m b/tikzit-1/src/common/RegularPolyShape.m new file mode 100644 index 0000000..3555115 --- /dev/null +++ b/tikzit-1/src/common/RegularPolyShape.m @@ -0,0 +1,76 @@ +// +// RegularPolyShape.m +// TikZiT +// +// Copyright 2011 Aleks Kissinger +// Copyright 2012 Alex Merry +// All rights reserved. +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "RegularPolyShape.h" +#import "Node.h" +#import "Edge.h" +#import "util.h" + +@implementation RegularPolyShape + +- (id)initWithSides:(int)sides rotation:(int)rotation { + self = [super init]; + if (self == nil) + return nil; + + // TikZ draws regular polygons using a radius inscribed + // _inside_ the shape (touching middles of edges), not + // outside (touching points) + const float innerRadius = 0.2f; + + NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:sides]; + NSMutableArray *edges = [NSMutableArray arrayWithCapacity:sides]; + + float dtheta = (M_PI * 2.0f) / ((float)sides); + float theta = (dtheta/2.0f) - (M_PI / 2.0f); + theta += degreesToRadians(rotation); + // radius of the outer circle + float radius = ABS(innerRadius / cos(dtheta)); + + for (int i = 0; i < sides; ++i) { + NSPoint p; + p.x = radius * cos(theta); + p.y = radius * sin(theta); + + [nodes addObject:[Node nodeWithPoint:p]]; + theta += dtheta; + } + + for (int i = 0; i < sides; ++i) { + [edges addObject:[Edge edgeWithSource:[nodes objectAtIndex:i] + andTarget:[nodes objectAtIndex:(i+1)%sides]]]; + } + + paths = [[NSSet alloc] initWithObjects:edges,nil]; + + styleTikz = [[NSString alloc] initWithFormat: + @"regular polygon,regular polygon sides=%d,shape border rotate=%d", + sides, rotation]; + + return self; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/RenderContext.h b/tikzit-1/src/common/RenderContext.h new file mode 100644 index 0000000..8633944 --- /dev/null +++ b/tikzit-1/src/common/RenderContext.h @@ -0,0 +1,156 @@ +/* + * Copyright 2011 Alex Merry <alex.merry@kdemail.net> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> +#import "RColor.h" + +typedef enum { + AntialiasDisabled, + AntialiasDefault +} AntialiasMode; + +// encapsulates a CTLine on OSX and +// a PangoLayout in GTK+ +@protocol TextLayout +@property (readonly) NSSize size; +@property (readonly) NSString *text; +- (void) showTextAt:(NSPoint)topLeft withColor:(RColor)color; +@end + +@protocol RenderContext +- (void) saveState; +- (void) restoreState; + +- (NSRect) clipBoundingBox; +- (BOOL) strokeIncludesPoint:(NSPoint)p; +- (BOOL) fillIncludesPoint:(NSPoint)p; +- (id<TextLayout>) layoutText:(NSString*)text withSize:(CGFloat)fontSize; + +// this may not affect text rendering +- (void) setAntialiasMode:(AntialiasMode)mode; +- (void) setLineWidth:(CGFloat)width; +// setting to 0 will unset the dash +- (void) setLineDash:(CGFloat)dashLength; + +/** + * Clear the current path, including all subpaths + */ +- (void) startPath; +/** + * Close the current subpath + */ +- (void) closeSubPath; +/** + * Start a new subpath, and set the current point. + * + * The point will be the current point and the starting point + * for the subpath. + */ +- (void) moveTo:(NSPoint)p; +/** + * Add a cubic bezier curve to the current subpath. + * + * The curve will start at the current point, terminate at end and + * be defined by cp1 and cp2. + */ +- (void) curveTo:(NSPoint)end withCp1:(NSPoint)cp1 andCp2:(NSPoint)cp2; +/** + * Add a straight line to the current subpath. + * + * The line will start at the current point, and terminate at end. + */ +- (void) lineTo:(NSPoint)end; +/** + * Add a new rectangular subpath. + * + * The current point is undefined after this call. + */ +- (void) rect:(NSRect)rect; +/** + * Add a new circular subpath. + * + * The current point is undefined after this call. + */ +- (void) circleAt:(NSPoint)c withRadius:(CGFloat)r; + +/** + * Paint along the current path. + * + * The current line width and dash style will be used, + * and the colour is given by color. + * + * The path will be cleared by this call, as though + * startPath had been called. + */ +- (void) strokePathWithColor:(RColor)color; +/** + * Paint inside the current path. + * + * The fill colour is given by color. + * + * The path will be cleared by this call, as though + * startPath had been called. + */ +- (void) fillPathWithColor:(RColor)color; +/** + * Paint along and inside the current path. + * + * The current line width and dash style will be used, + * and the colour is given by color. + * + * The path will be cleared by this call, as though + * startPath had been called. + * + * Note that the fill and stroke may overlap, although + * the stroke is always painted on top, so this is only + * relevant when the stroke colour has an alpha channel + * other than 1.0f. + */ +- (void) strokePathWithColor:(RColor)scolor + andFillWithColor:(RColor)fcolor; +/** + * Paint along and inside the current path using an alpha channel. + * + * The current line width and dash style will be used, + * and the colour is given by color. + * + * The path will be cleared by this call, as though + * startPath had been called. + * + * Note that the fill and stroke may overlap, although + * the stroke is always painted on top, so this is only + * relevant when the stroke colour has an alpha channel + * other than 1.0f. + */ +- (void) strokePathWithColor:(RColor)scolor + andFillWithColor:(RColor)fcolor + usingAlpha:(CGFloat)alpha; +/** + * Set the clip to the current path. + * + * The path will be cleared by this call, as though + * startPath had been called. + */ +- (void) clipToPath; + +/** + * Paint everywhere within the clip. + */ +- (void) paintWithColor:(RColor)color; +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Shape.h b/tikzit-1/src/common/Shape.h new file mode 100644 index 0000000..b401a87 --- /dev/null +++ b/tikzit-1/src/common/Shape.h @@ -0,0 +1,49 @@ +// +// Shape.h +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> +#import "Transformer.h" + +@interface Shape : NSObject <NSCopying> { + NSSet *paths; + NSRect boundingRect; // cache + NSString *styleTikz; +} + +@property (retain) NSSet *paths; +@property (readonly) NSRect boundingRect; +/** + * The tikz code to use in style properties for this shape + * + * This can return nil, in which case the shape name should be used + */ +@property (retain) NSString *styleTikz; + +- (id)init; ++ (void)refreshShapeDictionary; ++ (NSDictionary*)shapeDictionary; ++ (Shape*)shapeForName:(NSString*)shapeName; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Shape.m b/tikzit-1/src/common/Shape.m new file mode 100644 index 0000000..e887688 --- /dev/null +++ b/tikzit-1/src/common/Shape.m @@ -0,0 +1,171 @@ +// +// Shape.m +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "Shape.h" + +#import "Edge.h" +#import "SupportDir.h" +#import "ShapeNames.h" + +#import "CircleShape.h" +#import "DiamondShape.h" +#import "RectangleShape.h" +#import "RegularPolyShape.h" +#import "TikzShape.h" + +#import "util.h" + +@implementation Shape + +- (void)calcBoundingRect { + boundingRect = NSZeroRect; + + if (paths == nil) + return; + + for (NSArray *arr in paths) { + for (Edge *e in arr) { + boundingRect = NSUnionRect(boundingRect, [e boundingRect]); + } + } +} + +- (id)init { + self = [super init]; + if (self) { + paths = nil; + } + return self; +} + +- (NSSet*)paths {return paths;} +- (void)setPaths:(NSSet *)p { + if (paths != p) { +#if __has_feature(objc_arc) + paths = p; +#else + [paths release]; + paths = [p retain]; +#endif + [self calcBoundingRect]; + } +} + +- (NSRect)boundingRect { return boundingRect; } + +@synthesize styleTikz; + +- (id)copyWithZone:(NSZone*)zone { + Shape *cp = [[[self class] allocWithZone:zone] init]; + [cp setPaths:paths]; + [cp setStyleTikz:styleTikz]; + return cp; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [paths release]; + [styleTikz release]; + [super dealloc]; +#endif +} + +NSDictionary *shapeDictionary = nil; + ++ (void)addShapesInDir:(NSString*)shapeDir to:(NSMutableDictionary*)shapeDict { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSError *err = nil; + NSArray *files = [fileManager contentsOfDirectoryAtPath:shapeDir error:&err]; + + if (files != nil) { + NSString *nm; + for (NSString *f in files) { + if ([f hasSuffix:@".tikz"]) { + nm = [f substringToIndex:[f length]-5]; + TikzShape *sh = + [[TikzShape alloc] initWithTikzFile: + [shapeDir stringByAppendingPathComponent:f]]; + if (sh != nil) { + [shapeDict setObject:sh forKey:nm]; +#if ! __has_feature(objc_arc) + [sh release]; +#endif + } + } + } + } +} + ++ (void)refreshShapeDictionary { + Shape *shapes[5] = { + [[CircleShape alloc] init], + [[RectangleShape alloc] init], + [[DiamondShape alloc] init], + [[RegularPolyShape alloc] initWithSides:3 rotation:0], + [[RegularPolyShape alloc] initWithSides:3 rotation:180]}; + NSMutableDictionary *shapeDict = [[NSMutableDictionary alloc] initWithObjectsAndKeys: + shapes[0], SHAPE_CIRCLE, + shapes[1], SHAPE_RECTANGLE, + shapes[2], SHAPE_DIAMOND, + shapes[3], SHAPE_UP_TRIANGLE, + shapes[4], SHAPE_DOWN_TRIANGLE, + nil]; +#if ! __has_feature(objc_arc) + for (int i = 0; i<5; ++i) [shapes[i] release]; +#endif + + NSString *systemShapeDir = [[SupportDir systemSupportDir] stringByAppendingPathComponent:@"shapes"]; + NSString *userShapeDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"shapes"]; + + [Shape addShapesInDir:systemShapeDir to:shapeDict]; + [Shape addShapesInDir:userShapeDir to:shapeDict]; + + NSDictionary *oldShapeDictionary = shapeDictionary; + shapeDictionary = shapeDict; + + [[NSNotificationCenter defaultCenter] + postNotificationName:@"ShapeDictionaryReplaced" + object:self]; + +#if ! __has_feature(objc_arc) + [oldShapeDictionary release]; +#endif +} + ++ (NSDictionary*)shapeDictionary { + if (shapeDictionary == nil) [Shape refreshShapeDictionary]; + return shapeDictionary; +} + ++ (Shape*)shapeForName:(NSString*)shapeName { + Shape *s = [[[self shapeDictionary] objectForKey:shapeName] copy]; +#if __has_feature(objc_arc) + return s; +#else + return [s autorelease]; +#endif +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/ShapeNames.h b/tikzit-1/src/common/ShapeNames.h new file mode 100644 index 0000000..66ecfb1 --- /dev/null +++ b/tikzit-1/src/common/ShapeNames.h @@ -0,0 +1,27 @@ +// +// ShapeNames.h +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// + +#define SHAPE_CIRCLE @"circle" +#define SHAPE_RECTANGLE @"rectangle" +#define SHAPE_UP_TRIANGLE @"up triangle" +#define SHAPE_DOWN_TRIANGLE @"down triangle" +#define SHAPE_DIAMOND @"diamond" + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/StyleManager.h b/tikzit-1/src/common/StyleManager.h new file mode 100644 index 0000000..bc920e7 --- /dev/null +++ b/tikzit-1/src/common/StyleManager.h @@ -0,0 +1,49 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> +#import "NodeStyle.h" +#import "EdgeStyle.h" + +@interface StyleManager: NSObject <NSCopying> { + NSMutableArray *nodeStyles; + NSMutableArray *edgeStyles; +} + ++ (StyleManager*) manager; +- (id) init; + +@property (readonly) NSArray *nodeStyles; +@property (readonly) NSArray *edgeStyles; + +// only for use by loading code +- (void) _setNodeStyles:(NSMutableArray*)styles; +- (void) _setEdgeStyles:(NSMutableArray*)styles; + +- (NodeStyle*) nodeStyleForName:(NSString*)name; +- (EdgeStyle*) edgeStyleForName:(NSString*)name; + +- (void) addNodeStyle:(NodeStyle*)style; +- (void) removeNodeStyle:(NodeStyle*)style; +- (void) addEdgeStyle:(EdgeStyle*)style; +- (void) removeEdgeStyle:(EdgeStyle*)style; + +- (void) updateFromManager:(StyleManager*)manager; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/StyleManager.m b/tikzit-1/src/common/StyleManager.m new file mode 100644 index 0000000..05c6c86 --- /dev/null +++ b/tikzit-1/src/common/StyleManager.m @@ -0,0 +1,378 @@ +/* + * Copyright 2011 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "StyleManager.h" + +@implementation StyleManager + +- (void) nodeStylePropertyChanged:(NSNotification*)n { + if ([[[n userInfo] objectForKey:@"propertyName"] isEqual:@"name"]) { + NSDictionary *userInfo; + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + [n object], @"style", + [[n userInfo] objectForKey:@"oldValue"], @"oldName", + nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"NodeStyleRenamed" + object:self + userInfo:userInfo]; + } +} + +- (void) ignoreAllNodeStyles { + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:@"NodeStylePropertyChanged" + object:nil]; +} + +- (void) ignoreNodeStyle:(NodeStyle*)style { + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:@"NodeStylePropertyChanged" + object:style]; +} + +- (void) listenToNodeStyle:(NodeStyle*)style { + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(nodeStylePropertyChanged:) + name:@"NodeStylePropertyChanged" + object:style]; +} + +- (void) edgeStylePropertyChanged:(NSNotification*)n { + if ([[[n userInfo] objectForKey:@"propertyName"] isEqual:@"name"]) { + NSDictionary *userInfo; + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + [n object], @"style", + [[n userInfo] objectForKey:@"oldValue"], @"oldName", + nil]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"EdgeStyleRenamed" + object:self + userInfo:userInfo]; + } +} + +- (void) ignoreAllEdgeStyles { + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:@"EdgeStylePropertyChanged" + object:nil]; +} + +- (void) ignoreEdgeStyle:(EdgeStyle*)style { + [[NSNotificationCenter defaultCenter] + removeObserver:self + name:@"EdgeStylePropertyChanged" + object:style]; +} + +- (void) listenToEdgeStyle:(EdgeStyle*)style { + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(edgeStylePropertyChanged:) + name:@"EdgeStylePropertyChanged" + object:style]; +} + ++ (StyleManager*) manager { +#if __has_feature(objc_arc) + return [[self alloc] init]; +#else + return [[[self alloc] init] autorelease]; +#endif +} + +- (id) init { + self = [super init]; + + if (self) { + // we lazily load the default styles, since they may not be needed + nodeStyles = nil; + edgeStyles = nil; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +#if ! __has_feature(objc_arc) + [nodeStyles release]; + [edgeStyles release]; + + [super dealloc]; +#endif +} + +- (void) loadDefaultEdgeStyles { +#if ! __has_feature(objc_arc) + [edgeStyles release]; +#endif + edgeStyles = [[NSMutableArray alloc] initWithCapacity:3]; + + EdgeStyle *simple = [EdgeStyle defaultEdgeStyleWithName:@"simple"]; + [simple setThickness:2.0f]; + [self listenToEdgeStyle:simple]; + + EdgeStyle *arrow = [EdgeStyle defaultEdgeStyleWithName:@"arrow"]; + [arrow setThickness:2.0f]; + [arrow setDecorationStyle:ED_Arrow]; + [self listenToEdgeStyle:arrow]; + + EdgeStyle *tick = [EdgeStyle defaultEdgeStyleWithName:@"tick"]; + [tick setThickness:2.0f]; + [tick setDecorationStyle:ED_Tick]; + [self listenToEdgeStyle:tick]; + + [edgeStyles addObject:simple]; + [edgeStyles addObject:arrow]; + [edgeStyles addObject:tick]; +} + +- (void) loadDefaultNodeStyles { +#if ! __has_feature(objc_arc) + [nodeStyles release]; +#endif + nodeStyles = [[NSMutableArray alloc] initWithCapacity:3]; + + NodeStyle *rn = [NodeStyle defaultNodeStyleWithName:@"rn"]; + [rn setStrokeThickness:2]; + [rn setStrokeColorRGB:[ColorRGB colorWithFloatRed:0 green:0 blue:0]]; + [rn setFillColorRGB:[ColorRGB colorWithFloatRed:1 green:0 blue:0]]; + [self listenToNodeStyle:rn]; + + NodeStyle *gn = [NodeStyle defaultNodeStyleWithName:@"gn"]; + [gn setStrokeThickness:2]; + [gn setStrokeColorRGB:[ColorRGB colorWithFloatRed:0 green:0 blue:0]]; + [gn setFillColorRGB:[ColorRGB colorWithFloatRed:0 green:1 blue:0]]; + [self listenToNodeStyle:gn]; + + NodeStyle *yn = [NodeStyle defaultNodeStyleWithName:@"yn"]; + [yn setStrokeThickness:2]; + [yn setStrokeColorRGB:[ColorRGB colorWithFloatRed:0 green:0 blue:0]]; + [yn setFillColorRGB:[ColorRGB colorWithFloatRed:1 green:1 blue:0]]; + [self listenToNodeStyle:yn]; + + [nodeStyles addObject:rn]; + [nodeStyles addObject:gn]; + [nodeStyles addObject:yn]; +} + +- (void) postNodeStyleAdded:(NodeStyle*)style { + [[NSNotificationCenter defaultCenter] postNotificationName:@"NodeStyleAdded" + object:self + userInfo:[NSDictionary dictionaryWithObject:style forKey:@"style"]]; +} + +- (void) postNodeStyleRemoved:(NodeStyle*)style { + [[NSNotificationCenter defaultCenter] postNotificationName:@"NodeStyleRemoved" + object:self + userInfo:[NSDictionary dictionaryWithObject:style forKey:@"style"]]; +} + +- (void) postEdgeStyleAdded:(EdgeStyle*)style { + [[NSNotificationCenter defaultCenter] postNotificationName:@"EdgeStyleAdded" + object:self + userInfo:[NSDictionary dictionaryWithObject:style forKey:@"style"]]; +} + +- (void) postEdgeStyleRemoved:(EdgeStyle*)style { + [[NSNotificationCenter defaultCenter] postNotificationName:@"EdgeStyleRemoved" + object:self + userInfo:[NSDictionary dictionaryWithObject:style forKey:@"style"]]; +} + +- (void) postNodeStylesReplaced { + [[NSNotificationCenter defaultCenter] postNotificationName:@"NodeStylesReplaced" object:self]; +} + +- (void) postEdgeStylesReplaced { + [[NSNotificationCenter defaultCenter] postNotificationName:@"EdgeStylesReplaced" object:self]; +} + +- (NSArray*) nodeStyles { + if (nodeStyles == nil) { + [self loadDefaultNodeStyles]; + } + return nodeStyles; +} + +- (NSArray*) edgeStyles { + if (edgeStyles == nil) { + [self loadDefaultEdgeStyles]; + } + return edgeStyles; +} + +- (void) _setNodeStyles:(NSMutableArray*)styles { + [self ignoreAllNodeStyles]; +#if ! __has_feature(objc_arc) + [nodeStyles release]; + [styles retain]; +#endif + nodeStyles = styles; + for (NodeStyle *style in styles) { + [self listenToNodeStyle:style]; + } + [self postNodeStylesReplaced]; +} + +- (void) _setEdgeStyles:(NSMutableArray*)styles { + [self ignoreAllEdgeStyles]; +#if ! __has_feature(objc_arc) + [edgeStyles release]; + [styles retain]; +#endif + edgeStyles = styles; + for (EdgeStyle *style in styles) { + [self listenToEdgeStyle:style]; + } + [self postEdgeStylesReplaced]; +} + +- (NodeStyle*) nodeStyleForName:(NSString*)name { + for (NodeStyle *s in nodeStyles) { + if ([[s name] isEqualToString:name]) { + return s; + } + } + + return nil; +} + +- (void) addNodeStyle:(NodeStyle*)style { + if (nodeStyles == nil) { + [self loadDefaultNodeStyles]; + } + [nodeStyles addObject:style]; + [self listenToNodeStyle:style]; + [self postNodeStyleAdded:style]; +} + +- (void) removeNodeStyle:(NodeStyle*)style { + if (nodeStyles == nil) { + [self loadDefaultNodeStyles]; + } + + [self ignoreNodeStyle:style]; +#if ! __has_feature(objc_arc) + [style retain]; +#endif + [nodeStyles removeObject:style]; + [self postNodeStyleRemoved:style]; +#if ! __has_feature(objc_arc) + [style release]; +#endif +} + +- (EdgeStyle*) edgeStyleForName:(NSString*)name { + for (EdgeStyle *s in edgeStyles) { + if ([[s name] isEqualToString:name]) { + return s; + } + } + + return nil; +} + +- (void) addEdgeStyle:(EdgeStyle*)style { + if (edgeStyles == nil) { + [self loadDefaultEdgeStyles]; + } + [edgeStyles addObject:style]; + [self listenToEdgeStyle:style]; + [self postEdgeStyleAdded:style]; +} + +- (void) removeEdgeStyle:(EdgeStyle*)style { + if (edgeStyles == nil) { + [self loadDefaultEdgeStyles]; + } + + [self ignoreEdgeStyle:style]; +#if ! __has_feature(objc_arc) + [style retain]; +#endif + [edgeStyles removeObject:style]; + [self postEdgeStyleRemoved:style]; +#if ! __has_feature(objc_arc) + [style release]; +#endif +} + +- (void) updateFromManager:(StyleManager*)m { + NSMutableArray *ns = [NSMutableArray arrayWithCapacity:[[m nodeStyles] count]]; + for (NodeStyle *style in [m nodeStyles]) { + NodeStyle *currentStyle = [self nodeStyleForName:[style name]]; + if (currentStyle != nil) { + [currentStyle updateFromStyle:style]; + [ns addObject:currentStyle]; + } else { +#if __has_feature(objc_arc) + [ns addObject:[style copy]]; +#else + [ns addObject:[[style copy] autorelease]]; +#endif + } + } + NSMutableArray *es = [NSMutableArray arrayWithCapacity:[[m edgeStyles] count]]; + for (EdgeStyle *style in [m edgeStyles]) { + EdgeStyle *currentStyle = [self edgeStyleForName:[style name]]; + if (currentStyle != nil) { + [currentStyle updateFromStyle:style]; + [es addObject:currentStyle]; + } else { +#if __has_feature(objc_arc) + [es addObject:[style copy]]; +#else + [es addObject:[[style copy] autorelease]]; +#endif + } + } + [self _setNodeStyles:ns]; + [self _setEdgeStyles:es]; +} + +- (id) copyWithZone:(NSZone*)zone { + StyleManager *m = [[StyleManager allocWithZone:zone] init]; + + NSMutableArray *ns = [NSMutableArray arrayWithCapacity:[nodeStyles count]]; + for (NodeStyle *style in nodeStyles) { +#if __has_feature(objc_arc) + [ns addObject:[style copyWithZone:zone]]; +#else + [ns addObject:[[style copyWithZone:zone] autorelease]]; +#endif + } + NSMutableArray *es = [NSMutableArray arrayWithCapacity:[edgeStyles count]]; + for (EdgeStyle *style in edgeStyles) { +#if __has_feature(objc_arc) + [es addObject:[style copyWithZone:zone]]; +#else + [es addObject:[[style copyWithZone:zone] autorelease]]; +#endif + } + [m _setNodeStyles:ns]; + [m _setEdgeStyles:es]; + + return m; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/SupportDir.h b/tikzit-1/src/common/SupportDir.h new file mode 100644 index 0000000..30ccbcb --- /dev/null +++ b/tikzit-1/src/common/SupportDir.h @@ -0,0 +1,36 @@ +// +// SupportDir.h +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> + + +@interface SupportDir : NSObject { +} + ++ (void)createUserSupportDir; ++ (NSString*)userSupportDir; ++ (NSString*)systemSupportDir; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/SupportDir.m b/tikzit-1/src/common/SupportDir.m new file mode 100644 index 0000000..22fed1b --- /dev/null +++ b/tikzit-1/src/common/SupportDir.m @@ -0,0 +1,65 @@ +// +// SupportDir.m +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "SupportDir.h" + +#ifndef __APPLE__ +#import <glib.h> +#import "stat.h" +#endif + +@implementation SupportDir + ++ (NSString*)userSupportDir { +#ifdef __APPLE__ + return [[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory,NSUserDomainMask,YES) + objectAtIndex:0] stringByAppendingPathComponent:@"TikZiT"]; +#else + return [NSString stringWithFormat:@"%s/tikzit", g_get_user_config_dir ()]; +#endif +} + ++ (NSString*)systemSupportDir { +#ifdef __APPLE__ + return [[NSBundle mainBundle] resourcePath]; +#else + return @TIKZITSHAREDIR; +#endif +} + ++ (void)createUserSupportDir { +#ifdef __APPLE__ + NSFileManager *fileManager = [NSFileManager defaultManager]; + [fileManager createDirectoryAtPath:[SupportDir userSupportDir] + withIntermediateDirectories:YES + attributes:nil + error:NULL]; +#else + // NSFileManager is slightly dodgy on Windows + g_mkdir_with_parents ([[SupportDir userSupportDir] UTF8String], S_IRUSR | S_IWUSR | S_IXUSR); +#endif +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/TikzGraphAssembler+Parser.h b/tikzit-1/src/common/TikzGraphAssembler+Parser.h new file mode 100644 index 0000000..c9391a9 --- /dev/null +++ b/tikzit-1/src/common/TikzGraphAssembler+Parser.h @@ -0,0 +1,36 @@ +/* + * Copyright 2013 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * TikzGraphAssember+Parser.h + * + * This file exposes some TikzGraphAssembler functions + * that are only of use to the parser. + */ + +#import "TikzGraphAssembler.h" + +@interface TikzGraphAssembler (Parser) +- (Graph*) graph; +/** Store a node so that it can be looked up by name later */ +- (void) addNodeToMap:(Node*)n; +/** Get a previously-stored node by name */ +- (Node*) nodeWithName:(NSString*)name; +- (void) reportError:(const char *)message atLocation:(YYLTYPE*)yylloc; +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/TikzGraphAssembler.h b/tikzit-1/src/common/TikzGraphAssembler.h new file mode 100644 index 0000000..3403969 --- /dev/null +++ b/tikzit-1/src/common/TikzGraphAssembler.h @@ -0,0 +1,115 @@ +// +// TikzGraphAssembler.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> +#import "Graph.h" + +/** + * Parses (a subset of) tikz code and produces the corresponding Graph + * + * A note on errors: + * If parsing fails and a non-NULL error argument is given, it will be + * populated with an error with domain TZErrorDomain and code TZ_ERR_PARSE + * (see NSError+Tikzit.h). + * + * This will have a description set, typically something like + * "syntax error, unexpected [, expecting (" + * It may also have the following keys (it will have all or none of these), + * where numbers are stored using NSNumber: + * - startLine: the line (starting at 1) containing the first character + * of the bad token + * - startColumn: the column (starting at 1; tabs count for 1) of the first + * character of the bad token + * - endLine: the line (starting at 1) containing the last character + * of the bad token + * - endColumn: the column (starting at 1; tabs count for 1) of the last + * character of the bad token + * - syntaxString: an excerpt of the input string (typically the contents + * from startLine to endLine) providing some context + * - tokenOffset: the character offset (starting at 0) of the bad token + * within syntaxString + * - tokenLength: the character length (including newlines) of the bad token + * within syntaxString + */ +@interface TikzGraphAssembler : NSObject { + const char *tikzStr; + Graph *graph; + void *scanner; + NSMutableDictionary *nodeMap; + NSError *lastError; +} + +/** + * Parse tikz and place the result in gr + * + * Note that the graph must be empty; this might be used from an init + * method, for example, although don't forget that you can return a + * different object in init methods, providing you get the allocation + * right. + * + * @param tikz the tikz string to parse + * @param gr the graph to store the result in (must be empty, non-nil) + * @param e a location to store an error if parsing fails (may be NULL) + * @return YES if parsing succeeded, NO otherwise + */ ++ (BOOL) parseTikz:(NSString*)tikz forGraph:(Graph*)gr error:(NSError**)e; +/** + * Overload for -[parseTikz:forGraph:error:] with the error set to NULL + */ ++ (BOOL) parseTikz:(NSString*)tikz forGraph:(Graph*)gr; +/** + * Parse tikz + * + * @param tikz the tikz string to parse + * @param e a location to store an error if parsing fails (may be NULL) + * @return a Graph object if parsing succeeded, nil otherwise + */ ++ (Graph*) parseTikz:(NSString*)tikz error:(NSError**)e; +/** + * Overload for -[parseTikz:error:] with the error set to NULL + */ ++ (Graph*) parseTikz:(NSString*)tikz; +/** + * Validate a property string or value + * + * Wraps the string in "{" and "}" and checks it lexes completely; in other + * words, makes sure that "{" and "}" are balanced (ignoring escaped versions). + * @param tikz the string to validate + * @return YES if the string can be used as a property name or value, NO + * otherwise + */ ++ (BOOL)validateTikzPropertyNameOrValue:(NSString*)tikz; + +/** + * Validate an edge anchor + * + * Checks that the given string will successfully lex if used as an anchor for + * and edge + * @param tikz the string to validate + * @return YES if the string can be used as an edge anchor, NO otherwise + */ ++ (BOOL)validateTikzEdgeAnchor:(NSString*)tikz; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/TikzGraphAssembler.m b/tikzit-1/src/common/TikzGraphAssembler.m new file mode 100644 index 0000000..c5d2811 --- /dev/null +++ b/tikzit-1/src/common/TikzGraphAssembler.m @@ -0,0 +1,310 @@ +// +// TikzGraphAssembler.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "TikzGraphAssembler.h" +#import "tikzparserdefs.h" +#import "tikzparser.h" +#import "TikzGraphAssembler+Parser.h" +#import "tikzlexer.h" +#import "NSError+Tikzit.h" + +@implementation TikzGraphAssembler + +- (id)init { +#if ! __has_feature(objc_arc) + [self release]; +#endif + return nil; +} + +- (id)initWithGraph:(Graph*)g { + self = [super init]; + if (self) { +#if __has_feature(objc_arc) + graph = g; +#else + graph = [g retain]; +#endif + nodeMap = [[NSMutableDictionary alloc] init]; + yylex_init (&scanner); + yyset_extra(self, scanner); + } + return self; +} + +- (void)dealloc { +#if ! __has_feature(objc_arc) + [graph release]; + [nodeMap release]; + [lastError release]; + yylex_destroy (scanner); + [super dealloc]; +#endif +} + +- (BOOL) parseTikz:(NSString*)t error:(NSError**)error { +#if ! __has_feature(objc_arc) + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; +#endif + + tikzStr = [t UTF8String]; + yy_scan_string(tikzStr, scanner); + int result = yyparse(scanner); + tikzStr = NULL; + +#if ! __has_feature(objc_arc) + [pool drain]; +#endif + + if (result == 0) { + return YES; + } else { + if (error) { + if (lastError) { +#if __has_feature(objc_arc) + *error = lastError; +#else + *error = [[lastError retain] autorelease]; +#endif + } else if (result == 1) { + *error = [NSError errorWithMessage:@"Syntax error" + code:TZ_ERR_PARSE]; + } else if (result == 2) { + *error = [NSError errorWithMessage:@"Insufficient memory" + code:TZ_ERR_PARSE]; + } else { + *error = [NSError errorWithMessage:@"Unknown error" + code:TZ_ERR_PARSE]; + } + } + return NO; + } +} + ++ (BOOL) parseTikz:(NSString*)tikz forGraph:(Graph*)gr { + return [self parseTikz:tikz forGraph:gr error:NULL]; +} ++ (Graph*) parseTikz:(NSString*)tikz error:(NSError**)e { + Graph *gr = [[Graph alloc] init]; + if ([self parseTikz:tikz forGraph:gr error:e]) { +#if __has_feature(objc_arc) + return gr; +#else + return [gr autorelease]; +#endif + } else { +#if ! __has_feature(objc_arc) + [gr release]; +#endif + return nil; + } +} ++ (Graph*) parseTikz:(NSString*)tikz { + return [self parseTikz:tikz error:NULL]; +} + ++ (BOOL) parseTikz:(NSString*)tikz forGraph:(Graph*)gr error:(NSError**)error { + if([tikz length] == 0) { + // empty string -> empty graph + return YES; + } + + TikzGraphAssembler *assembler = [[self alloc] initWithGraph:gr]; + BOOL success = [assembler parseTikz:tikz error:error]; +#if ! __has_feature(objc_arc) + [assembler release]; +#endif + return success; +} + ++ (BOOL)validateTikzPropertyNameOrValue:(NSString*)tikz { + BOOL valid; + + NSString * testTikz = [NSString stringWithFormat: @"{%@}", tikz]; + + void *scanner; + yylex_init (&scanner); + yyset_extra(nil, scanner); + yy_scan_string([testTikz UTF8String], scanner); + YYSTYPE lval; + YYLTYPE lloc; + int result = yylex(&lval, &lloc, scanner); + valid = (result == DELIMITEDSTRING) && + (yyget_leng(scanner) == [testTikz length]); + yylex_destroy(scanner); + + return valid; +} + ++ (BOOL)validateTikzEdgeAnchor:(NSString*)tikz { + if ([tikz length] == 0) + return YES; + + BOOL valid = YES; + + NSString * testTikz = [NSString stringWithFormat: @"(1.%@)", tikz]; + + void *scanner; + yylex_init (&scanner); + yyset_extra(nil, scanner); + yy_scan_string([testTikz UTF8String], scanner); + YYSTYPE lval; + YYLTYPE lloc; + valid = valid && (yylex(&lval, &lloc, scanner) == LEFTPARENTHESIS); + valid = valid && (yylex(&lval, &lloc, scanner) == REFSTRING); + valid = valid && (yylex(&lval, &lloc, scanner) == FULLSTOP); + valid = valid && (yylex(&lval, &lloc, scanner) == REFSTRING); + valid = valid && (yylex(&lval, &lloc, scanner) == RIGHTPARENTHESIS); + valid = valid && (lloc.last_column == [testTikz length]); + yylex_destroy(scanner); + + return valid; +} + +@end + +@implementation TikzGraphAssembler (Parser) +- (Graph*)graph { return graph; } + +- (void)addNodeToMap:(Node*)n { + [nodeMap setObject:n forKey:[n name]]; +} + +- (Node*)nodeWithName:(NSString*)name { + return [nodeMap objectForKey:name]; +} + +- (void) setLastError:(NSError*)error { +#if ! __has_feature(objc_arc) + [error retain]; + [lastError release]; +#endif + lastError = error; +} + +- (void) reportError:(const char *)message atLocation:(YYLTYPE*)yylloc { + NSString *nsmsg = [NSString stringWithUTF8String:message]; + + const char *first_line_start = find_start_of_nth_line ( + tikzStr, yylloc->first_line - 1); + const char *last_line_start = find_start_of_nth_line ( + first_line_start, yylloc->last_line - yylloc->first_line); + const char *last_line_end = last_line_start; + while (*last_line_end && *last_line_end != '\n') { + // points to just after end of last line + ++last_line_end; + } + + size_t context_len = last_line_end - first_line_start; + size_t token_offset = yylloc->first_column - 1; + size_t token_len = ((last_line_start - first_line_start) + yylloc->last_column) - token_offset; + + if (token_offset + token_len > context_len) { + // error position state is corrupted + NSLog(@"Got bad error state for error \"%s\": start(%i,%i), end(%i,%i)", + message, + yylloc->first_line, + yylloc->first_column, + yylloc->last_line, + yylloc->last_column); + [self setLastError:[NSError errorWithMessage:nsmsg + code:TZ_ERR_PARSE]]; + } else { + char *context = malloc (context_len + 1); + strncpy (context, first_line_start, context_len); + *(context + context_len) = '\0'; + + NSDictionary *userInfo = + [NSDictionary dictionaryWithObjectsAndKeys: + nsmsg, + NSLocalizedDescriptionKey, + [NSNumber numberWithInt:yylloc->first_line], + @"startLine", + [NSNumber numberWithInt:yylloc->first_column], + @"startColumn", + [NSNumber numberWithInt:yylloc->last_line], + @"endLine", + [NSNumber numberWithInt:yylloc->last_column], + @"endColumn", + [NSString stringWithUTF8String:context], + @"syntaxString", + [NSNumber numberWithInt:token_offset], + @"tokenStart", + [NSNumber numberWithInt:token_len], + @"tokenLength", + nil]; + [self setLastError: + [NSError errorWithDomain:TZErrorDomain + code:TZ_ERR_PARSE + userInfo:userInfo]]; + + // we can now freely edit context string + // we only bother printing out the first line + if (yylloc->last_line > yylloc->first_line) { + char *nlp = strchr(context, '\n'); + if (nlp) { + *nlp = '\0'; + context_len = nlp - context; + NSAssert2(token_offset < context_len, @"token_offset (%lu) < context_len (%lu)", token_offset, context_len); + if (token_offset + token_len > context_len) { + token_len = context_len - token_offset; + } + } else { + NSLog(@"Didn't find any newlines in context string!"); + } + } + size_t token_col_offset = 0; + size_t token_col_len = 0; + for (int i = 0; i < token_offset; ++i) { + if (*(context + i) == '\t') + token_col_offset += 8; + else + ++token_col_offset; + } + for (int i = token_offset; i < token_offset + token_len; ++i) { + if (*(context + i) == '\t') + token_col_len += 8; + else + ++token_col_len; + } + NSString *pointerLinePadding = + [@"" stringByPaddingToLength:token_col_offset + withString:@" " + startingAtIndex:0]; + NSString *pointerLineCarets = + [@"" stringByPaddingToLength:token_col_len + withString:@"^" + startingAtIndex:0]; + NSLog(@"Parse error on line %i, starting at %i: %s\n%s\n%@%@", + yylloc->first_line, + yylloc->first_column, + message, + context, + pointerLinePadding, + pointerLineCarets); + free (context); + } +} +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/TikzShape.h b/tikzit-1/src/common/TikzShape.h new file mode 100644 index 0000000..6a91f91 --- /dev/null +++ b/tikzit-1/src/common/TikzShape.h @@ -0,0 +1,37 @@ +// +// TikzShape.h +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import <Foundation/Foundation.h> +#import "Shape.h" + +@interface TikzShape : Shape { + NSString *tikzSrc; +} + +@property (copy) NSString *tikzSrc; + +- (id)initWithTikzFile:(NSString*)file; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/TikzShape.m b/tikzit-1/src/common/TikzShape.m new file mode 100644 index 0000000..555a7df --- /dev/null +++ b/tikzit-1/src/common/TikzShape.m @@ -0,0 +1,70 @@ +// +// TikzShape.m +// TikZiT +// +// Copyright 2011 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "TikzShape.h" +#import "Graph.h" + +@implementation TikzShape + +@synthesize tikzSrc; + +- (id)initWithTikzFile:(NSString*)file { + self = [super init]; + if (self) { + NSString *tikz = [NSString stringWithContentsOfFile:file + encoding:NSUTF8StringEncoding + error:NULL]; + if (tikz == nil) return nil; + + tikzSrc = [tikz copy]; + + Graph *graph = [Graph graphFromTikz:tikz]; + if (graph == nil) return nil; + + NSRect graphBounds = ([graph hasBoundingBox]) ? [graph boundingBox] : [graph bounds]; + + float sz = 0.5f; + + // the "screen" coordinate space fits in the shape bounds + Transformer *t = [Transformer transformer]; + float width_ratio = (2*sz) / graphBounds.size.width; + float height_ratio = (2*sz) / graphBounds.size.height; + [t setScale:MIN(width_ratio, height_ratio)]; + NSRect bds = [t rectToScreen:graphBounds]; + NSPoint shift = NSMakePoint(-NSMidX(bds), + -NSMidY(bds)); + [t setOrigin:shift]; + [graph applyTransformer:t]; +#if __has_feature(objc_arc) + paths = [graph pathCover]; +#else + paths = [[graph pathCover] retain]; +#endif + } + return self; +} + + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/Transformer.h b/tikzit-1/src/common/Transformer.h new file mode 100644 index 0000000..1b0108a --- /dev/null +++ b/tikzit-1/src/common/Transformer.h @@ -0,0 +1,154 @@ +// +// Transformer.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + + +#import <Foundation/Foundation.h> + +extern float const PIXELS_PER_UNIT; + +/*! + @class Transformer + @brief Do affine coordinate transforms between an abstract co-ordinate + space (such as the graph's) and the screen's. + + This currently allows zooming and panning. + */ +@interface Transformer : NSObject <NSCopying> { + NSPoint origin; + float x_scale; + float y_scale; +} + +/*! + @brief The screen co-ordinate of the abstract space origin. + */ +@property (assign) NSPoint origin; + +/*! + @brief The scale (from abstract space to screen space) + @detail This is the size of a single unit (a distance of 1.0) + of the abstract space on the screen. + + Around 50 is a reasonable value. + */ +@property (assign) float scale; + +/*! + @brief Whether co-ordinates are flipped about the X axis + @detail TikZ considers X co-ordinates to run left to right, + which is not necessarily how the screen views + them. + */ +@property (assign,getter=isFlippedAboutXAxis) BOOL flippedAboutXAxis; + +/*! + @brief Whether co-ordinates are flipped about the Y axis + @detail TikZ considers Y co-ordinates to run up the page, + which is not necessarily how the screen views + them. + */ +@property (assign,getter=isFlippedAboutYAxis) BOOL flippedAboutYAxis; + +/*! + @brief Transform a point from screen space to abstract space. + @param p a point in screen space. + @result A point in abstract space. + */ +- (NSPoint)fromScreen:(NSPoint)p; + +/*! + @brief Transform a point from abstract space to screen space. + @param p a point in abstract space. + @result A point in screen space. + */ +- (NSPoint)toScreen:(NSPoint)p; + +/*! + @brief Scale a distance from screen space to abstract space. + @param dist a distance in screen space. + @result A distance in abstract space. + */ +- (float)scaleFromScreen:(float)dist; + +/*! + @brief Scale a distance from abstract space to screen space. + @param dist a distance in abstract space. + @result A distance in screen space. + */ +- (float)scaleToScreen:(float)dist; + +/*! + @brief Scale a rectangle from screen space to abstract space. + @param r a rectangle in screen space. + @result A rectangle in abstract space. + */ +- (NSRect)rectFromScreen:(NSRect)r; + +/*! + @brief Scale a rectangle from abstract space to screen space. + @param r a rectangle in abstract space. + @result A rectangle in screen space. + */ +- (NSRect)rectToScreen:(NSRect)r; + +/*! + @brief Factory method to get an identity transformer. + @result A transformer. + */ ++ (Transformer*)transformer; + +/*! + @brief Factory method to get a transformer identical to another + @result A transformer. + */ ++ (Transformer*)transformerWithTransformer:(Transformer*)t; + +/*! + @brief Factory method to get a transformer. + @param o The screen co-ordinate of the abstract space origin + @param scale The scale (from abstract space to screen space) + @result A transformer. + */ ++ (Transformer*)transformerWithOrigin:(NSPoint)o andScale:(float)scale; + +/*! + @brief Get a global 'actual size' transformer. + @result A transformer. + */ ++ (Transformer*)defaultTransformer; + +/*! + @brief A transformer set up from two bounding rects. + + graphRect is made as large as possible while still fitting into screenRect. + @result A transformer. + */ ++ (Transformer*)transformerToFit:(NSRect)graphRect intoScreenRect:(NSRect)screenRect flippedAboutXAxis:(BOOL)flipX flippedAboutYAxis:(BOOL)flipY; + ++ (Transformer*)transformerToFit:(NSRect)graphRect intoScreenRect:(NSRect)screenRect flippedAboutXAxis:(BOOL)flipX; ++ (Transformer*)transformerToFit:(NSRect)graphRect intoScreenRect:(NSRect)screenRect flippedAboutYAxis:(BOOL)flipY; ++ (Transformer*)transformerToFit:(NSRect)graphRect intoScreenRect:(NSRect)screenRect; + +@end + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/Transformer.m b/tikzit-1/src/common/Transformer.m new file mode 100644 index 0000000..2b56813 --- /dev/null +++ b/tikzit-1/src/common/Transformer.m @@ -0,0 +1,231 @@ +// +// Transformer.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "Transformer.h" + +float const PIXELS_PER_UNIT = 50; + +@implementation Transformer + ++ (Transformer*)transformer { +#if __has_feature(objc_arc) + return [[Transformer alloc] init]; +#else + return [[[Transformer alloc] init] autorelease]; +#endif +} + ++ (Transformer*)transformerWithTransformer:(Transformer*)t { +#if __has_feature(objc_arc) + return [t copy]; +#else + return [[t copy] autorelease]; +#endif +} + ++ (Transformer*)transformerWithOrigin:(NSPoint)o andScale:(float)scale { + Transformer *trans = [self transformer]; + [trans setOrigin:o]; + [trans setScale:scale]; + return trans; +} + ++ (Transformer*)transformerToFit:(NSRect)graphRect + intoScreenRect:(NSRect)screenRect { + return [self transformerToFit:graphRect + intoScreenRect:screenRect + flippedAboutXAxis:NO + flippedAboutYAxis:NO]; +} + ++ (Transformer*)transformerToFit:(NSRect)graphRect + intoScreenRect:(NSRect)screenRect + flippedAboutXAxis:(BOOL)flipX { + return [self transformerToFit:graphRect + intoScreenRect:screenRect + flippedAboutXAxis:flipX + flippedAboutYAxis:NO]; +} + ++ (Transformer*)transformerToFit:(NSRect)graphRect + intoScreenRect:(NSRect)screenRect + flippedAboutYAxis:(BOOL)flipY { + return [self transformerToFit:graphRect + intoScreenRect:screenRect + flippedAboutXAxis:NO + flippedAboutYAxis:flipY]; +} + ++ (Transformer*)transformerToFit:(NSRect)graphRect + intoScreenRect:(NSRect)screenRect + flippedAboutXAxis:(BOOL)flipAboutXAxis + flippedAboutYAxis:(BOOL)flipAboutYAxis { + + const float wscale = screenRect.size.width / graphRect.size.width; + const float hscale = screenRect.size.height / graphRect.size.height; + const float scale = (wscale < hscale) ? wscale : hscale; + const float xpad = (screenRect.size.width - (graphRect.size.width * scale)) / 2.0; + const float ypad = (screenRect.size.height - (graphRect.size.height * scale)) / 2.0; + + // if we are flipping, we need to calculate the origin from the opposite edge + const float gx = flipAboutYAxis ? -(graphRect.size.width + graphRect.origin.x) + : graphRect.origin.x; + const float gy = flipAboutXAxis ? -(graphRect.size.height + graphRect.origin.y) + : graphRect.origin.y; + const float origin_x = screenRect.origin.x - (gx * scale) + xpad; + const float origin_y = screenRect.origin.y - (gy * scale) + ypad; + + Transformer *trans = [self transformer]; + [trans setOrigin:NSMakePoint(origin_x, origin_y)]; + [trans setScale:scale]; + [trans setFlippedAboutXAxis:flipAboutXAxis]; + [trans setFlippedAboutYAxis:flipAboutYAxis]; + return trans; +} + +- (id) init { + self = [super init]; + + if (self) { + origin = NSZeroPoint; + x_scale = 1.0f; + y_scale = 1.0f; + } + + return self; +} + +- (id)copyWithZone:(NSZone *)zone { + Transformer *cp = [[[self class] allocWithZone:zone] init]; + if (cp) { + cp->origin = origin; + cp->x_scale = x_scale; + cp->y_scale = y_scale; + } + return cp; +} + +- (NSPoint)origin { return origin; } +- (void)setOrigin:(NSPoint)o { + origin = o; +} + +- (float)scale { return ABS(x_scale); } +- (void)setScale:(float)s { + x_scale = (x_scale < 0.0) ? -s : s; + y_scale = (y_scale < 0.0) ? -s : s; +} + +- (BOOL)isFlippedAboutXAxis { + return y_scale < 0.0; +} + +- (void)setFlippedAboutXAxis:(BOOL)flip { + if (flip != [self isFlippedAboutXAxis]) { + y_scale *= -1; + } +} + +- (BOOL)isFlippedAboutYAxis { + return x_scale < 0.0; +} + +- (void)setFlippedAboutYAxis:(BOOL)flip { + if (flip != [self isFlippedAboutYAxis]) { + x_scale *= -1; + } +} + +- (NSPoint)fromScreen:(NSPoint)p { + NSPoint trans; + trans.x = (p.x - origin.x) / x_scale; + trans.y = (p.y - origin.y) / y_scale; + return trans; +} + +- (NSPoint)toScreen:(NSPoint)p { + NSPoint trans; + trans.x = (p.x * x_scale) + origin.x; + trans.y = (p.y * y_scale) + origin.y; + return trans; +} + +- (float)scaleFromScreen:(float)dist { + return dist / ABS(x_scale); +} + +- (float)scaleToScreen:(float)dist { + return dist * ABS(x_scale); +} + +- (NSRect)rectFromScreen:(NSRect)r { + NSRect r1; + r1.origin = [self fromScreen:r.origin]; + r1.size.width = [self scaleFromScreen:r.size.width]; + r1.size.height = [self scaleFromScreen:r.size.height]; + // if we're flipped, the origin will be at a different corner + if ([self isFlippedAboutYAxis]) { + r1.origin.x -= r1.size.width; + } + if ([self isFlippedAboutXAxis]) { + r1.origin.y -= r1.size.height; + } + return r1; +} + +- (NSRect)rectToScreen:(NSRect)r { + NSPoint o = r.origin; + // if we're flipped, the origin will be at a different corner + if ([self isFlippedAboutYAxis]) { + o.x = NSMaxX(r); + } + if ([self isFlippedAboutXAxis]) { + o.y = NSMaxY(r); + } + NSRect r1; + r1.origin = [self toScreen:o]; + r1.size.width = [self scaleToScreen:r.size.width]; + r1.size.height = [self scaleToScreen:r.size.height]; + return r1; +} + +- (BOOL)isEqual:(id)object { + Transformer *t = (Transformer*)object; + return ([t origin].x == [self origin].x && + [t origin].y == [self origin].y && + [t scale] == [self scale]); +} + +Transformer *defaultTransformer = nil; + ++ (Transformer*)defaultTransformer { + if (defaultTransformer == nil) { + defaultTransformer = [[Transformer alloc] init]; + [defaultTransformer setScale:PIXELS_PER_UNIT]; + } + return defaultTransformer; +} + +@end + +// vi:ft=objc:ts=4:noet:sts=4:sw=4 diff --git a/tikzit-1/src/common/test/Makefile b/tikzit-1/src/common/test/Makefile new file mode 100644 index 0000000..d158d16 --- /dev/null +++ b/tikzit-1/src/common/test/Makefile @@ -0,0 +1,14 @@ +OBJC = gcc -MMD -MP -DSTAND_ALONE -DGNUSTEP -DGNUSTEP_BASE_LIBRARY=1 -DGNU_RUNTIME=1 -DGNUSTEP_BASE_LIBRARY=1 -fno-strict-aliasing -fPIC -Wall -DGSWARN -DGSDIAGNOSE -Wno-import -O0 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic -fgnu-runtime -fconstant-string-class=NSConstantString -I. -I.. -I/users/alemer/GNUstep/Library/Headers -std=c99 -D_GNU_SOURCE -rdynamic -fgnu-runtime -L/users/alemer/GNUstep/Library/Libraries -L/usr/local/lib64 -L/usr/lib64 -lgnustep-base -lpthread -lobjc -lm + +maths_test_objects = test.m maths.m ../util.m +color_test_objects = test.m color.m ../ColorRGB.m ../util.m ../BasicMapTable.m ../RColor.m + +test: maths-test color-test + ./maths-test + ./color-test + +maths-test: $(maths_test_objects) + $(OBJC) $(maths_test_objects) -o $@ + +color-test: $(color_test_objects) + $(OBJC) $(color_test_objects) -o $@ diff --git a/tikzit-1/src/common/test/color.m b/tikzit-1/src/common/test/color.m new file mode 100644 index 0000000..48a6ff4 --- /dev/null +++ b/tikzit-1/src/common/test/color.m @@ -0,0 +1,80 @@ +// +// color.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// +#import "test/test.h" +#import "ColorRGB.h" + +#ifdef STAND_ALONE +void runTests() { +#else +void testColor() { +#endif + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"color"); + + ColorRGB *red = [ColorRGB colorWithRed:255 green:0 blue:0]; + ColorRGB *lime = [ColorRGB colorWithRed:0 green:255 blue:0]; + ColorRGB *green = [ColorRGB colorWithRed:0 green:128 blue:0]; + TEST(@"Recognised red", + [red name] != nil && + [[red name] isEqualToString:@"Red"]); + TEST(@"Recognised lime", + [lime name] != nil && + [[lime name] isEqualToString:@"Lime"]); + TEST(@"Recognised green", + [green name] != nil && + [[green name] isEqualToString:@"Green"]); + + ColorRGB *floatRed = [ColorRGB colorWithFloatRed:1.0f green:0.0f blue:0.0f]; + ColorRGB *floatLime = [ColorRGB colorWithFloatRed:0.0f green:1.0f blue:0.0f]; + ColorRGB *floatGreen = [ColorRGB colorWithFloatRed:0.0f green:0.5f blue:0.0f]; + + TEST(@"Float red equal to int red", [floatRed isEqual:red]); + TEST(@"Float lime equal to int lime", [floatLime isEqual:lime]); + TEST(@"Float green equal to int green", [floatGreen isEqual:green]); + + TEST(@"Recognised float red", + [floatRed name] != nil && + [[floatRed name] isEqualToString:@"Red"]); + + TEST(@"Recognised float lime", + [floatLime name] != nil && + [[floatLime name] isEqualToString:@"Lime"]); + + TEST(@"Recognised float green", + [floatGreen name] != nil && + [[floatGreen name] isEqualToString:@"Green"]); + + [floatRed setRedFloat:0.99f]; + TEST(@"Nudged red, not recognised now", [floatRed name] == nil); + [floatRed setToClosestHashed]; + TEST(@"Set to closest hashed, reconised again", + [floatRed name] != nil && + [[floatRed name] isEqualToString:@"Red"]); + + TEST(@"Red has correct hex (ff0000)", [[red hexName] isEqualToString:@"hexcolor0xff0000"]); + TEST(@"Lime has correct hex (00ff00)", [[lime hexName] isEqualToString:@"hexcolor0x00ff00"]); + TEST(@"Green has correct hex (008000)", [[green hexName] isEqualToString:@"hexcolor0x008000"]); + + endTestBlock(@"color"); + [pool drain]; +} diff --git a/tikzit-1/src/common/test/common.m b/tikzit-1/src/common/test/common.m new file mode 100644 index 0000000..c9ac980 --- /dev/null +++ b/tikzit-1/src/common/test/common.m @@ -0,0 +1,34 @@ +// +// common.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// +#import "test/test.h" +void testParser(); +void testColor(); +void testMaths(); + +void testCommon() { + startTestBlock(@"common"); + testParser(); + testColor(); + testMaths(); + endTestBlock(@"common"); +} diff --git a/tikzit-1/src/common/test/maths.m b/tikzit-1/src/common/test/maths.m new file mode 100644 index 0000000..a11e58e --- /dev/null +++ b/tikzit-1/src/common/test/maths.m @@ -0,0 +1,562 @@ +// +// TikZiT +// +// Copyright 2011 Alex Merry +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// + +#import "../util.h" + +#import "test.h" + +void testRectAroundPoints() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"NSRectAroundPoints"); + + NSRect rect = NSRectAroundPoints (NSZeroPoint, NSZeroPoint); + assertRectsEqual (@"(0,0) and (0,0)", rect, NSZeroRect); + + rect = NSRectAroundPoints (NSZeroPoint, NSMakePoint (1.0f, 1.0f)); + assertRectsEqual (@"(0,0) and (1,1)", rect, NSMakeRect (0.0f, 0.0f, 1.0f, 1.0f)); + + rect = NSRectAroundPoints (NSMakePoint (-1.0f, 1.0f), NSMakePoint (1.0f, -1.0f)); + assertRectsEqual (@"(-1,1) and (1,-1)", rect, NSMakeRect (-1.0f, -1.0f, 2.0f, 2.0f)); + + endTestBlock(@"NSRectAroundPoints"); + [pool drain]; +} + +void testRectAroundPointsWithPadding() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"NSRectAroundPointsWithPadding"); + + NSRect rect = NSRectAroundPointsWithPadding (NSZeroPoint, NSZeroPoint, 0.0f); + assertRectsEqual (@"(0,0) and (0,0); 0 padding", rect, NSZeroRect); + + rect = NSRectAroundPointsWithPadding (NSZeroPoint, NSZeroPoint, 0.2f); + assertRectsEqual (@"(0,0) and (0,0); 0.2 padding", rect, NSMakeRect (-0.2f, -0.2f, 0.4f, 0.4f)); + + rect = NSRectAroundPointsWithPadding (NSZeroPoint, NSMakePoint (1.0f, 1.0f), -0.2f); + assertRectsEqual (@"(0,0) and (1,1); -0.2 padding", rect, NSMakeRect (0.2f, 0.2f, 0.6f, 0.6f)); + + endTestBlock(@"NSRectAroundPointsWithPadding"); + [pool drain]; +} + +void testGoodAtan() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"good_atan"); + + assertFloatsEqual (@"0.0, 0.0", good_atan (0.0f, 0.0f), 0.0f); + assertFloatsEqual (@"0.0, 1.0", good_atan (0.0f, 1.0f), 0.5f * M_PI); + assertFloatsEqual (@"0.0, -1.0", good_atan (0.0f, -1.0f), 1.5f * M_PI); + assertFloatsEqual (@"1.0, 0.0", good_atan (1.0f, 0.0f), 0.0f); + assertFloatsEqual (@"1.0, 0.1", good_atan (1.0f, 0.1f), 0.0996687f); + + endTestBlock(@"good_atan"); + [pool drain]; +} + +void testBezierInterpolate() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"bezierInterpolate"); + + assertFloatsEqual (@"0.0, (0.0, 0.1, 0.2, 0.3)", bezierInterpolate (0.0f, 0.0f, 0.1f, 0.2f, 0.3f), 0.0f); + assertFloatsEqual (@"1.0, (0.0, 0.1, 0.2, 0.3)", bezierInterpolate (1.0f, 0.0f, 0.1f, 0.2f, 0.3f), 0.3f); + assertFloatsEqual (@"0.5, (0.0, 0.1, 0.2, 0.3)", bezierInterpolate (0.5f, 0.0f, 0.1f, 0.2f, 0.3f), 0.15f); + // FIXME: other tests + + endTestBlock(@"bezierInterpolate"); + [pool drain]; +} + +void testLineSegmentsIntersect() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"lineSegmentsIntersect"); + + BOOL result = NO; + NSPoint intersection = NSMakePoint (-1.0f, -1.0f); + + result = lineSegmentsIntersect (NSMakePoint (-1.0f, -1.0f), NSMakePoint (1.0f, 1.0f), + NSMakePoint (-1.0f, 1.0f), NSMakePoint (1.0f, -1.0f), + &intersection); + TEST (@"Cross at zero: has intersection", result); + assertPointsEqual (@"Cross at zero: intersection value", intersection, NSZeroPoint); + + result = lineSegmentsIntersect (NSMakePoint (-1.0f, -1.0f), NSMakePoint (-0.5f, -0.5f), + NSMakePoint (-1.0f, 1.0f), NSMakePoint (1.0f, -1.0f), + &intersection); + TEST (@"Fail to cross at zero", !result); + + result = lineSegmentsIntersect (NSMakePoint (1.0f, 1.0f), NSMakePoint (1.0f, -1.0f), + NSMakePoint (0.0f, 0.0f), NSMakePoint (1.0f, 0.0f), + &intersection); + TEST (@"Touch at one: has intersection", result); + assertPointsEqual (@"Touch at one: intersection value", intersection, NSMakePoint (1.0f, 0.0f)); + + endTestBlock(@"lineSegmentsIntersect"); + [pool drain]; +} + +void testLineSegmentIntersectsRect() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"lineSegmentIntersectsRect"); + + BOOL result = NO; + + result = lineSegmentIntersectsRect ( + NSMakePoint (-1.0f, -1.0f), + NSMakePoint (0.0f, 0.0f), + NSZeroRect); + TEST (@"Zero rect; line touches zero", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (-1.0f, -1.0f), + NSMakePoint (-0.1f, -0.1f), + NSZeroRect); + TEST (@"Zero rect; line short of zero", !result); + + NSRect rect = NSMakeRect (1.0f, 1.0f, 1.0f, 1.0f); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 0.0f), + NSMakePoint (3.0f, 1.0f), + rect); + TEST (@"Line underneath", !result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 0.0f), + NSMakePoint (1.0f, 3.0f), + rect); + TEST (@"Line to left", !result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 2.0f), + NSMakePoint (3.0f, 3.0f), + rect); + TEST (@"Line above", !result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (2.0f, 0.0f), + NSMakePoint (3.0f, 3.0f), + rect); + TEST (@"Line to right", !result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 0.0f), + NSMakePoint (0.9f, 0.9f), + rect); + TEST (@"Line short", !result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (1.1f, 1.1f), + NSMakePoint (1.9f, 1.9f), + rect); + TEST (@"Line inside", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 1.5f), + NSMakePoint (3.0f, 1.5f), + rect); + TEST (@"Horizontal line through", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (1.5f, 0.0f), + NSMakePoint (1.5f, 3.0f), + rect); + TEST (@"Vertical line through", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.5f, 1.0f), + NSMakePoint (2.0f, 2.5f), + rect); + TEST (@"Cut top and left", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (2.0f, 0.5f), + NSMakePoint (0.5f, 2.0f), + rect); + TEST (@"Cut bottom and left", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (1.0f, 0.5f), + NSMakePoint (2.5f, 2.0f), + rect); + TEST (@"Cut bottom and right", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 1.0f), + NSMakePoint (2.0f, 3.0f), + rect); + TEST (@"Touch top left", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (1.0f, 0.0f), + NSMakePoint (3.0f, 2.0f), + rect); + TEST (@"Touch bottom right", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (1.0f, 0.0f), + NSMakePoint (1.0f, 3.0f), + rect); + TEST (@"Along left side", result); + + result = lineSegmentIntersectsRect ( + NSMakePoint (0.0f, 1.0f), + NSMakePoint (3.0f, 1.0f), + rect); + TEST (@"Along bottom side", result); + + endTestBlock(@"lineSegmentIntersectsRect"); + [pool drain]; +} + +struct line_bezier_test { + NSString *msg; + NSPoint lstart; + NSPoint lend; + NSPoint c0; + NSPoint c1; + NSPoint c2; + NSPoint c3; + BOOL expectedResult; + float expectedT; + NSPoint expectedIntersect; +}; + +static struct line_bezier_test line_bezier_tests[] = { + { + @"Outside box", + {0.0f, 0.0f}, + {1.0f, 0.0f}, + {0.0f, 1.0f}, + {0.0f, 2.0f}, + {1.0f, 2.0f}, + {1.0f, 1.0f}, + NO, + -1.0f, + {0.0f, 0.0f} + }, + { + @"Single intersect", + {100.0f, 20.0f}, + {195.0f, 255.0f}, + {93.0f, 163.0f}, + {40.0f, 30.0f}, + {270.0f, 115.0f}, + {219.0f, 178.0f}, + YES, + -0.4f, + {129.391693f, 92.705772f} + }, + { + @"Double intersect", + {100.0f, 20.0f}, + {195.0f, 255.0f}, + {93.0f, 163.0f}, + {40.0f, 30.0f}, + {270.0f, 115.0f}, + {154.0f, 212.0f}, + YES, + -0.909f, + {170.740646f,194.990021f} + }, + { + @"Near miss", + {100.0f, 20.0f}, + {195.0f, 255.0f}, + {93.0f, 163.0f}, + {40.0f, 30.0f}, + {176.0f, 100.0f}, + {154.0f, 212.0f}, + NO, + -1.0f, + {0.0f,0.0f} + } +}; +static unsigned int n_line_bezier_tests = sizeof (line_bezier_tests) / sizeof (line_bezier_tests[0]); + +void testLineSegmentIntersectsBezier() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"lineSegmentIntersectsBezier"); + + for (unsigned int i = 0; i < n_line_bezier_tests; ++i) { + NSPoint intersect; + BOOL result = lineSegmentIntersectsBezier ( + line_bezier_tests[i].lstart, + line_bezier_tests[i].lend, + line_bezier_tests[i].c0, + line_bezier_tests[i].c1, + line_bezier_tests[i].c2, + line_bezier_tests[i].c3, + &intersect); + if (result) { + if (line_bezier_tests[i].expectedT < 0.0f) { + assertPointsEqual (line_bezier_tests[i].msg, intersect, line_bezier_tests[i].expectedIntersect); + } else { + assertPointsEqual (line_bezier_tests[i].msg, intersect, + bezierInterpolateFull (line_bezier_tests[i].expectedT, line_bezier_tests[i].c0, line_bezier_tests[i].c1, line_bezier_tests[i].c2, line_bezier_tests[i].c3)); + } + } else { + if (line_bezier_tests[i].expectedResult) + fail (line_bezier_tests[i].msg); + else + pass (line_bezier_tests[i].msg); + } + } + +BOOL lineSegmentIntersectsBezier (NSPoint lstart, NSPoint lend, NSPoint c0, NSPoint c1, NSPoint c2, NSPoint c3, NSPoint *result); + endTestBlock(@"lineSegmentIntersectsBezier"); + [pool drain]; +} + +struct exit_point_test { + NSString *msg; + NSPoint rayStart; + float angle; + NSRect rect; + NSPoint expected; +}; + +static struct exit_point_test exit_point_tests[] = { + { + @"0.0 rads", + {0.0f, 0.0f}, + 0.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {1.0f, 0.0f} + }, + { + @"pi/2 rads", + {0.0f, 0.0f}, + M_PI / 2.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {0.0f, 1.0f} + }, + { + @"-pi/2 rads", + {0.0f, 0.0f}, + -M_PI / 2.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {0.0f, -1.0f} + }, + { + @"pi rads", + {0.0f, 0.0f}, + M_PI, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-1.0f, 0.0f} + }, + { + @"-pi rads", + {0.0f, 0.0f}, + -M_PI, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-1.0f, 0.0f} + }, + { + @"pi/4 rads", + {0.0f, 0.0f}, + M_PI / 4.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {1.0f, 1.0f} + }, + { + @"3pi/4 rads", + {0.0f, 0.0f}, + (3.0f * M_PI) / 4.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-1.0f, 1.0f} + }, + { + @"-pi/4 rads", + {0.0f, 0.0f}, + -M_PI / 4.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {1.0f, -1.0f} + }, + { + @"-3pi/4 rads", + {0.0f, 0.0f}, + (-3.0f * M_PI) / 4.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-1.0f, -1.0f} + }, + { + @"pi/8 rads", + {0.0f, 0.0f}, + M_PI / 8.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {1.0f, 0.414213562373095f} + }, + { + @"3pi/8 rads", + {0.0f, 0.0f}, + 3.0f * M_PI / 8.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {0.414213562373095f, 1.0f} + }, + { + @"-5pi/8 rads", + {0.0f, 0.0f}, + -5.0f * M_PI / 8.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-0.414213562373095f, -1.0f} + }, + { + @"-7pi/8 rads", + {0.0f, 0.0f}, + -7.0f * M_PI / 8.0f, + {{-1.0f, -1.0f}, {2.0f, 2.0f}}, + {-1.0f, -0.414213562373095f} + }, + { + @"pi/8 rads; origin (1,1)", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{0.0f, 0.0f}, {2.0f, 2.0f}}, + {2.0f, 1.414213562373095f} + }, + { + @"7pi/8 rads; origin (-2,2)", + {-2.0f, 2.0f}, + 7.0f * M_PI / 8.0f, + {{-3.0f, 1.0f}, {2.0f, 2.0f}}, + {-3.0f, 2.414213562373095f} + }, + { + @"pi/8 rads; origin (1,1); SW of box", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{1.0f, 1.0f}, {1.0f, 1.0f}}, + {2.0f, 1.414213562373095f} + }, + { + @"pi/8 rads; origin (1,1); SE of box", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{0.0f, 1.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"pi/8 rads; origin (1,1); NE of box", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{0.0f, 1.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"pi/8 rads; origin (1,1); NW of box", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{1.0f, 0.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"pi/8 rads; origin (1,1); N of box", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{0.5f, 0.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"7pi/8 rads; origin (1,1); N of box", + {1.0f, 1.0f}, + 7.0f * M_PI / 8.0f, + {{0.5f, 0.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"-pi/8 rads; origin (1,1); S of box", + {1.0f, 1.0f}, + -M_PI / 8.0f, + {{0.5f, 1.0f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"-pi/8 rads; origin (1,1); E of box", + {1.0f, 1.0f}, + -M_PI / 8.0f, + {{0.0f, 0.5f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"-7pi/8 rads; origin (1,1); W of box", + {1.0f, 1.0f}, + -7.0f * M_PI / 8.0f, + {{1.0f, 0.5f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"7pi/8 rads; origin (1,1); W of box", + {1.0f, 1.0f}, + 7.0f * M_PI / 8.0f, + {{1.0f, 0.5f}, {1.0f, 1.0f}}, + {1.0f, 1.0f} + }, + { + @"pi/8 rads; origin (1,1); leave through top", + {1.0f, 1.0f}, + M_PI / 8.0f, + {{0.9f, 0.1f}, {1.0f, 1.0f}}, + {1.2414213562373f, 1.1f} + }, + { + @"0 rads; origin (1,1); N of box", + {1.0f, 1.0f}, + 0.0f, + {{0.5f, 0.0f}, {1.0f, 1.0f}}, + {1.5f, 1.0f} + } +}; +static unsigned int n_exit_point_tests = sizeof (exit_point_tests) / sizeof (exit_point_tests[0]); + +void testFindExitPointOfRay() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"findExitPointOfRay"); + + for (unsigned int i = 0; i < n_exit_point_tests; ++i) { + NSPoint exitPoint = findExitPointOfRay ( + exit_point_tests[i].rayStart, + exit_point_tests[i].angle, + exit_point_tests[i].rect); + assertPointsEqual (exit_point_tests[i].msg, exitPoint, exit_point_tests[i].expected); + } + + endTestBlock(@"findExitPointOfRay"); + [pool drain]; +} + +#ifdef STAND_ALONE +void runTests() { +#else +void testMaths() { +#endif + startTestBlock(@"maths"); + testRectAroundPoints(); + testRectAroundPointsWithPadding(); + testGoodAtan(); + testBezierInterpolate(); + testLineSegmentsIntersect(); + testLineSegmentIntersectsRect(); + testFindExitPointOfRay(); + testLineSegmentIntersectsBezier(); + endTestBlock(@"maths"); +} + +// vim:ft=objc:ts=4:sts=4:sw=4:noet diff --git a/tikzit-1/src/common/test/parser.m b/tikzit-1/src/common/test/parser.m new file mode 100644 index 0000000..3346acd --- /dev/null +++ b/tikzit-1/src/common/test/parser.m @@ -0,0 +1,86 @@ +// +// parser.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// +#import "test/test.h" +#import "TikzGraphAssembler.h" + + +#ifdef STAND_ALONE +void runTests() { +#else +void testParser() { +#endif + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + startTestBlock(@"parser"); + + [TikzGraphAssembler setup]; + + NodeStyle *rn = [NodeStyle defaultNodeStyleWithName:@"rn"]; + NSArray *styles = [NSArray arrayWithObject:rn]; + + NSString *tikz = + @"\\begin{tikzpicture}[dotpic]" + @" \\begin{pgfonlayer}{foo}" //ignored + @" \\node [style=rn] (0) at (-2,3.4) {stuff{$\\alpha$ in here}};" + @" \\node (b) at (1,1) {};" + @" \\end{pgfonlayer}" //ignored + @" \\draw [bend right=20] (0) to node[tick]{-} (b.center);" + @"\\end{tikzpicture}"; + + TikzGraphAssembler *ga = [[TikzGraphAssembler alloc] init]; + TEST(@"Parsing TikZ", [ga parseTikz:tikz]); + + Graph *g = [ga graph]; + TEST(@"Graph is non-nil", g != nil); + TEST(@"Graph has correct number of nodes", [[g nodes] count]==2); + TEST(@"Graph has correct number of edges", [[g edges] count]==1); + + NSEnumerator *en = [[g nodes] objectEnumerator]; + Node *n; + Node *n1, *n2; + while ((n=[en nextObject])) { + [n attachStyleFromTable:styles]; + if ([n style] == rn) n1 = n; + else if ([n style] == nil) n2 = n; + } + + TEST(@"Styles attached correctly", n1!=nil && n2!=nil); + + TEST(@"Nodes labeled correctly", + [[n1 label] isEqualToString:@"stuff{$\\alpha$ in here}"] && + [[n2 label] isEqualToString:@""] + ); + + Edge *e1 = [[[g edges] objectEnumerator] nextObject]; + + TEST(@"Edge has edge node", [e1 edgeNode]!=nil); + TEST(@"Edge node labeled correctly", [[[e1 edgeNode] label] isEqualToString:@"-"]); +// NSString *sty = [[[[[e1 edgeNode] data] atoms] objectEnumerator] nextObject]; +// TEST(@"Edge node styled correctly", sty!=nil && [sty isEqualToString:@"tick"]); + + PUTS(@"Source anchor: %@",[e1 sourceAnchor]); + PUTS(@"Target anchor: %@",[e1 targetAnchor]); + + endTestBlock(@"parser"); + + [pool drain]; +} diff --git a/tikzit-1/src/common/test/test.h b/tikzit-1/src/common/test/test.h new file mode 100644 index 0000000..59dcdd4 --- /dev/null +++ b/tikzit-1/src/common/test/test.h @@ -0,0 +1,57 @@ +// +// test.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// +#import <Foundation/Foundation.h> + +@interface Allocator : NSObject +{} +@end + +BOOL fuzzyCompare (float f1, float f2); +BOOL fuzzyComparePoints (NSPoint p1, NSPoint p2); + +void setColorEnabled(BOOL b); + +void pass(NSString *msg); +void fail(NSString *msg); +void TEST(NSString *msg, BOOL test); +void assertRectsEqual (NSString *msg, NSRect val, NSRect exp); +void assertPointsEqual (NSString *msg, NSPoint val, NSPoint exp); +void assertFloatsEqual (NSString *msg, float val, float exp); + +void startTests(); +void endTests(); + +void startTestBlock(NSString *name); +void endTestBlock(NSString *name); + +#define PUTS(fmt, ...) { \ + NSString *_str = [[NSString alloc] initWithFormat:fmt, ##__VA_ARGS__]; \ + printf("%s\n", [_str UTF8String]); \ + [_str release]; } + +#define failFmt(fmt, ...) { \ + NSString *_fstr = [[NSString alloc] initWithFormat:fmt, ##__VA_ARGS__]; \ + fail(_fstr); \ + [_fstr release]; } + +// vim:ft=objc:ts=4:sts=4:sw=4:noet diff --git a/tikzit-1/src/common/test/test.m b/tikzit-1/src/common/test/test.m new file mode 100644 index 0000000..9afcd67 --- /dev/null +++ b/tikzit-1/src/common/test/test.m @@ -0,0 +1,175 @@ +// +// test.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with TikZiT. If not, see <http://www.gnu.org/licenses/>. +// +#import "test/test.h" + +static int PASSES; +static int FAILS; + +static int ALLOC_INSTANCES = 0; + +static BOOL colorEnabled = YES; +static int depth = 0; + +static NSString *RED, *GREEN, *BLUE, *OFF; + +static NSString *indents[6] = + {@"", @" ", @" ", @" ", + @" ", @" "}; + +#define INDENT ((depth >= 6) ? indents[5] : indents[depth]) + + +@implementation Allocator + ++ (id)alloc { + ++ALLOC_INSTANCES; + return [super alloc]; +} + +- (void)dealloc { + --ALLOC_INSTANCES; + [super dealloc]; +} + ++ (Allocator*)allocator { + return [[[Allocator alloc] init] autorelease]; +} + +@end + +BOOL fuzzyCompare(float f1, float f2) { + return (ABS(f1 - f2) <= 0.00001f * MAX(1.0f,MIN(ABS(f1), ABS(f2)))); +} + +BOOL fuzzyComparePoints (NSPoint p1, NSPoint p2) { + return fuzzyCompare (p1.x, p2.x) && fuzzyCompare (p1.y, p2.y); +} + +void pass(NSString *msg) { + PUTS(@"%@[%@PASS%@] %@", INDENT, GREEN, OFF, msg); + ++PASSES; +} + +void fail(NSString *msg) { + PUTS(@"%@[%@FAIL%@] %@", INDENT, RED, OFF, msg); + ++FAILS; +} + +void TEST(NSString *msg, BOOL test) { + if (test) { + pass (msg); + } else { + fail (msg); + } +} + +void assertRectsEqual (NSString *msg, NSRect r1, NSRect r2) { + BOOL equal = fuzzyCompare (r1.origin.x, r2.origin.x) && + fuzzyCompare (r1.origin.y, r2.origin.y) && + fuzzyCompare (r1.size.width, r2.size.width) && + fuzzyCompare (r1.size.height, r2.size.height); + if (equal) { + pass (msg); + } else { + failFmt(@"%@ (expected (%f,%f:%fx%f) but got (%f,%f:%fx%f))", + msg, + r2.origin.x, r2.origin.y, r2.size.width, r2.size.height, + r1.origin.x, r1.origin.y, r1.size.width, r1.size.height); + } +} + +void assertPointsEqual (NSString *msg, NSPoint p1, NSPoint p2) { + BOOL equal = fuzzyCompare (p1.x, p2.x) && fuzzyCompare (p1.y, p2.y); + if (equal) { + pass (msg); + } else { + failFmt(@"%@ (expected (%f,%f) but got (%f,%f)", + msg, + p2.x, p2.y, + p1.x, p1.y); + } +} + +void assertFloatsEqual (NSString *msg, float f1, float f2) { + if (fuzzyCompare (f1, f2)) { + pass (msg); + } else { + failFmt(@"%@ (expected %f but got %f", msg, f2, f1); + } +} + +void startTests() { + PASSES = 0; + FAILS = 0; +} + +void endTests() { + PUTS(@"Done testing. %@%d%@ passed, %@%d%@ failed.", + GREEN, PASSES, OFF, + RED, FAILS, OFF); +} + +void startTestBlock(NSString *name) { + PUTS(@"%@Starting %@%@%@ tests.", INDENT, BLUE, name, OFF); + ++depth; +} + +void endTestBlock(NSString *name) { + --depth; + PUTS(@"%@Done with %@%@%@ tests.", INDENT, BLUE, name, OFF); +} + +void setColorEnabled(BOOL b) { + colorEnabled = b; + if (b) { + RED = @"\033[31;1m"; + GREEN = @"\033[32;1m"; + BLUE = @"\033[36;1m"; + OFF = @"\033[0m"; + } else { + RED = @""; + GREEN = @""; + BLUE = @""; + OFF = @""; + } +} + +#ifdef STAND_ALONE +void runTests(); + +int main() { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + setColorEnabled (NO); + startTests(); + + runTests(); + + endTests(); + + [pool drain]; + return 0; +} +#endif + +// vim:ft=objc:ts=4:sts=4:sw=4:noet diff --git a/tikzit-1/src/common/tikzlexer.lm b/tikzit-1/src/common/tikzlexer.lm new file mode 100644 index 0000000..1e92f73 --- /dev/null +++ b/tikzit-1/src/common/tikzlexer.lm @@ -0,0 +1,170 @@ +%{ +/* + * Copyright 2010 Chris Heunen + * Copyright 2010-2013 Aleks Kissinger + * Copyright 2013 K. Johan Paulsson + * Copyright 2013 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import <Foundation/Foundation.h> +#import "tikzparserdefs.h" +#import "tikzparser.h" + +#define YY_USER_ACTION \ + yylloc->first_line = yylloc->last_line; \ + yylloc->first_column = yylloc->last_column + 1; \ + yylloc->last_column = yylloc->first_column + yyleng - 1; + +%} + +%option reentrant bison-bridge bison-locations 8bit +%option nounput +%option yylineno +%option noyywrap +%option header-file="common/tikzlexer.h" +%option extra-type="TikzGraphAssembler *" + + +%s props +%s xcoord +%s ycoord +%s noderef + +FLOAT \-?[0-9]*(\.[0-9]+)? + +%% + + /* whitespace is ignored, except for position counting; we don't + count formfeed and vtab as whitespace, because it's not obvious + how they should be dealt with and no-one actually uses them */ + + /* lex will take the longest-matching string */ +<INITIAL,xcoord,ycoord,props,noderef>\r\n|\r|\n { + yylloc->first_line += 1; + yylloc->last_line = yylloc->first_line; + yylloc->first_column = yylloc->last_column = 0; +} +<INITIAL,xcoord,ycoord,props,noderef>[\t ]+ { } + +\\begin\{tikzpicture\} { return BEGIN_TIKZPICTURE_CMD; } +\\end\{tikzpicture\} { return END_TIKZPICTURE_CMD; } +\\begin\{pgfonlayer\} { return BEGIN_PGFONLAYER_CMD; } +\\end\{pgfonlayer\} { return END_PGFONLAYER_CMD; } +\\draw { return DRAW_CMD; } +\\node { return NODE_CMD; } +\\path { return PATH_CMD; } +rectangle { return RECTANGLE; } +node { return NODE; } +at { return AT; } +to { return TO; } +; { return SEMICOLON; } + +\([ ]*{FLOAT}[ ]*,[ ]*{FLOAT}[ ]*\) { + yylloc->last_column = yylloc->first_column + 1; + yyless(1); + BEGIN(xcoord); +} +<xcoord>{FLOAT} { + yylval->pt.x=(float)strtod(yytext,NULL); + BEGIN(ycoord); +} +<ycoord>, { } +<ycoord>{FLOAT} { + yylval->pt.y=(float)strtod(yytext,NULL); +} +<ycoord>\) { + BEGIN(INITIAL); + return COORD; +} + + /* when we see "[", change parsing mode */ +\[ /*syntaxhlfix]*/ { + BEGIN(props); + return LEFTBRACKET; +} +<props>= { return EQUALS; } +<props>, { return COMMA; } + /* technically, it is possible to have newlines in the middle of + property names or values, but in practice this is unlikely and + screws up our line counting */ +<props>[^=,\{\] \t\n]([^=,\{\]\n]*[^=,\{\] \t\n])? { + yylval->nsstr=[NSString stringWithUTF8String:yytext]; + return PROPSTRING; +} +<props>\] { + BEGIN(INITIAL); + return RIGHTBRACKET; +} + +\( { + BEGIN(noderef); + return LEFTPARENTHESIS; +} +<noderef>\. { + return FULLSTOP; +} + /* we assume node names (and anchor names) never contain + newlines */ +<noderef>[^\.\{\)\n]+ { + yylval->nsstr=[NSString stringWithUTF8String:yytext]; + return REFSTRING; +} +<noderef>\) { + BEGIN(INITIAL); + return RIGHTPARENTHESIS; +} + +<INITIAL,props>\{ { + NSMutableString *buf = [NSMutableString string]; + unsigned int brace_depth = 1; + unsigned int escape = 0; + while (1) { + char c = input(yyscanner); + // eof reached before closing brace + if (c == '\0' || c == EOF) { + return UNCLOSED_DELIM_STR; + } + + yylloc->last_column += 1; + yyleng += 1; + if (escape) { + escape = 0; + } else if (c == '\\') { + escape = 1; + } else if (c == '{') { + brace_depth++; + } else if (c == '}') { + brace_depth--; + if (brace_depth == 0) break; + } else if (c == '\n') { + yylloc->last_line += 1; + yylloc->last_column = 0; + } + [buf appendFormat:@"%c", c]; + } + + yylval->nsstr = buf; + return DELIMITEDSTRING; +} + +\\begin { return UNKNOWN_BEGIN_CMD; } +\\end { return UNKNOWN_END_CMD; } +\\[a-zA-Z0-9]+ { return UNKNOWN_CMD; } +[a-zA-Z0-9]+ { return UNKNOWN_STR; } +<INITIAL,xcoord,ycoord,props,noderef>. { return UNKNOWN_STR; } + + /* vi:ft=lex:noet:ts=4:sts=4:sw=4: + */ diff --git a/tikzit-1/src/common/tikzparser.ym b/tikzit-1/src/common/tikzparser.ym new file mode 100644 index 0000000..344e969 --- /dev/null +++ b/tikzit-1/src/common/tikzparser.ym @@ -0,0 +1,224 @@ +%{ +/* + * Copyright 2010 Chris Heunen + * Copyright 2010-2013 Aleks Kissinger + * Copyright 2013 K. Johan Paulsson + * Copyright 2013 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#import "tikzparserdefs.h" +%} + +/* we use features added to bison 2.4 */ +%require "2.3" + +%error-verbose +/* enable maintaining locations for better error messages */ +%locations +/* the name of the header file */ +/*%defines "common/tikzparser.h"*/ +/* make it re-entrant (no global variables) */ +%pure-parser +/* We use a pure (re-entrant) lexer. This means yylex + will take a void* (opaque) type to maintain its state */ +%lex-param {void *scanner} +/* Since this parser is also pure, yyparse needs to take + that lexer state as an argument */ +%parse-param {void *scanner} + +/* possible data types for semantic values */ +%union { + NSString *nsstr; + GraphElementProperty *prop; + GraphElementData *data; + Node *node; + NSPoint pt; + struct noderef noderef; +} + +%{ +#import "GraphElementData.h" +#import "GraphElementProperty.h" +#import "Node.h" +#import "Edge.h" + +#import "tikzlexer.h" +#import "TikzGraphAssembler+Parser.h" +/* the assembler (used by this parser) is stored in the lexer + state as "extra" data */ +#define assembler yyget_extra(scanner) + +/* pass errors off to the assembler */ +void yyerror(YYLTYPE *yylloc, void *scanner, const char *str) { + [assembler reportError:str atLocation:yylloc]; +} +%} + +/* yyloc is set up with first_column = last_column = 1 by default; + however, it makes more sense to think of us being "before the + start of the line" before we parse anything */ +%initial-action { + yylloc.first_column = yylloc.last_column = 0; +} + + +%token BEGIN_TIKZPICTURE_CMD "\\begin{tikzpicture}" +%token END_TIKZPICTURE_CMD "\\end{tikzpicture}" +%token BEGIN_PGFONLAYER_CMD "\\begin{pgfonlayer}" +%token END_PGFONLAYER_CMD "\\end{pgfonlayer}" +%token DRAW_CMD "\\draw" +%token NODE_CMD "\\node" +%token PATH_CMD "\\path" +%token RECTANGLE "rectangle" +%token NODE "node" +%token AT "at" +%token TO "to" +%token SEMICOLON ";" +%token COMMA "," + +%token LEFTPARENTHESIS "(" +%token RIGHTPARENTHESIS ")" +%token LEFTBRACKET "[" +%token RIGHTBRACKET "]" +%token FULLSTOP "." +%token EQUALS "=" +%token <pt> COORD "co-ordinate" +%token <nsstr> PROPSTRING "key/value string" +%token <nsstr> REFSTRING "string" +%token <nsstr> DELIMITEDSTRING "{-delimited string" + +%token UNKNOWN_BEGIN_CMD "unknown \\begin command" +%token UNKNOWN_END_CMD "unknown \\end command" +%token UNKNOWN_CMD "unknown latex command" +%token UNKNOWN_STR "unknown string" +%token UNCLOSED_DELIM_STR "unclosed {-delimited string" + +%type<nsstr> nodename +%type<nsstr> optanchor +%type<nsstr> val +%type<prop> property +%type<data> extraproperties +%type<data> properties +%type<data> optproperties +%type<node> optedgenode +%type<noderef> noderef +%type<noderef> optnoderef + +%% + +tikzpicture: "\\begin{tikzpicture}" optproperties tikzcmds "\\end{tikzpicture}" + { + if ($2) { + [[assembler graph] setData:$2]; + } + }; +tikzcmds: tikzcmds tikzcmd | ; +tikzcmd: node | edge | boundingbox | ignore; + +ignore: "\\begin{pgfonlayer}" DELIMITEDSTRING | "\\end{pgfonlayer}"; + +optproperties: + "[" "]" + { $$ = nil; } + | "[" properties "]" + { $$ = $2; } + | { $$ = nil; }; +properties: extraproperties property + { + [$1 addObject:$2]; + $$ = $1; + }; +extraproperties: + extraproperties property "," + { + [$1 addObject:$2]; + $$ = $1; + } + | { $$ = [GraphElementData data]; }; +property: + val "=" val + { $$ = [GraphElementProperty property:$1 withValue:$3]; } + | val + { $$ = [GraphElementProperty atom:$1]; }; +val: PROPSTRING { $$ = $1; } | DELIMITEDSTRING { $$ = $1; }; + +nodename: "(" REFSTRING ")" { $$ = $2; }; +node: "\\node" optproperties nodename "at" COORD DELIMITEDSTRING ";" + { + Node *node = [[Node alloc] init]; + if ($2) + [node setData:$2]; + [node setName:$3]; + [node setPoint:$5]; + [node setLabel:$6]; + [assembler addNodeToMap:node]; + [[assembler graph] addNode:node]; +#if ! __has_feature(objc_arc) + [node release]; +#endif + }; + +optanchor: { $$ = nil; } | "." REFSTRING { $$ = $2; }; +noderef: "(" REFSTRING optanchor ")" + { + $$.node = [assembler nodeWithName:$2]; + $$.anchor = $3; + }; +optnoderef: + noderef { $$ = $1; } + | "(" ")" { $$.node = nil; $$.anchor = nil; } +optedgenode: + { $$ = nil; } + | "node" optproperties DELIMITEDSTRING + { + $$ = [Node node]; + if ($2) + [$$ setData:$2]; + [$$ setLabel:$3]; + } +edge: "\\draw" optproperties noderef "to" optedgenode optnoderef ";" + { + Edge *edge = [[Edge alloc] init]; + if ($2) + [edge setData:$2]; + [edge setSource:$3.node]; + [edge setSourceAnchor:$3.anchor]; + [edge setEdgeNode:$5]; + if ($6.node) { + [edge setTarget:$6.node]; + [edge setTargetAnchor:$6.anchor]; + } else { + [edge setTarget:$3.node]; + [edge setTargetAnchor:$3.anchor]; + } + [edge setAttributesFromData]; + [[assembler graph] addEdge:edge]; +#if ! __has_feature(objc_arc) + [edge release]; +#endif + }; + +ignoreprop: val | val "=" val; +ignoreprops: ignoreprop ignoreprops | ; +optignoreprops: "[" ignoreprops "]"; +boundingbox: + "\\path" optignoreprops COORD "rectangle" COORD ";" + { + [[assembler graph] setBoundingBox:NSRectAroundPoints($3, $5)]; + }; + +/* vi:ft=yacc:noet:ts=4:sts=4:sw=4 +*/ diff --git a/tikzit-1/src/common/tikzparserdefs.h b/tikzit-1/src/common/tikzparserdefs.h new file mode 100644 index 0000000..cde3345 --- /dev/null +++ b/tikzit-1/src/common/tikzparserdefs.h @@ -0,0 +1,49 @@ +/* + * Copyright 2013 Alex Merry <dev@randomguy3.me.uk> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +/* + * This file sets up some defs (particularly struct noderef) needed for + * the tikz parser and its users. + * + * It is needed because we wish to support bison 2.3, which is the + * version shipped with OSX. bison 2.4 onwards allows us to put this + * stuff in a "%code requires" block, where it will be put in the + * generated header file by bison. + * + * All the types used by the %union directive in tikzparser.ym should + * be declared, defined or imported here. + */ + +// Foundation has NSPoint and NSString +#import <Foundation/Foundation.h> + +@class TikzGraphAssembler; +@class GraphElementData; +@class GraphElementProperty; +@class Node; + +struct noderef { +#if __has_feature(objc_arc) + __unsafe_unretained Node *node; + __unsafe_unretained NSString *anchor; +#else + Node *node; + NSString *anchor; +#endif +}; + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/common/util.h b/tikzit-1/src/common/util.h new file mode 100644 index 0000000..b34f25d --- /dev/null +++ b/tikzit-1/src/common/util.h @@ -0,0 +1,201 @@ +// +// util.h +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// + +#import <Foundation/Foundation.h> + +#include <math.h> + +#ifndef M_PI +#define M_PI 3.141592654 +#endif + +#ifndef MAX +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +/*! + @brief Compute a bounding rectangle for two given points. + @param p1 a point. + @param p2 another point. + @result A bounding rectangle for p1 and p2. + */ +NSRect NSRectAroundPoints(NSPoint p1, NSPoint p2); + +/*! + @brief Compute a bounding rectangle for two given points. + @param rect the base rectangle + @param the point to ensure is included + @result A rectangle containing rect and p + */ +NSRect NSRectWithPoint(NSRect rect, NSPoint p); + +/*! + @brief Compute a bounding rectangle for two given points with a given padding. + @param p1 a point. + @param p2 another point. + @param padding a padding. + @result A bounding rectangle for p1 and p2 with padding. + */ +NSRect NSRectAroundPointsWithPadding(NSPoint p1, NSPoint p2, float padding); + +/*! + @brief Compute a bounding rectangle for four given points. + @result A bounding rectangle for p1, p2, p3 and p4. + */ +NSRect NSRectAround4Points(NSPoint p1, NSPoint p2, NSPoint p3, NSPoint p4); + +/*! + @brief Compute a bounding rectangle for four given points. + @param padding the amount to pad the rectangle + @result A bounding rectangle for p1, p2, p3 and p4 with padding + */ +NSRect NSRectAround4PointsWithPadding(NSPoint p1, NSPoint p2, NSPoint p3, NSPoint p4, float padding); + +/*! + @brief Find the distance between two points + @param p1 The first point + @param p2 The second point + @result The distance between p1 and p2 + */ +float NSDistanceBetweenPoints(NSPoint p1, NSPoint p2); + +/*! + @brief Compute the 'real' arctan for two points. Always succeeds and gives a good angle, + regardless of sign, zeroes, etc. + @param dx the x distance between points. + @param dy the y distance between points. + @result An angle in radians. + */ +float good_atan(float dx, float dy); + +/*! + @brief Interpolate along a bezier curve to the given distance. To find the x coord, + use the relavant x coordinates for c0-c3, and for y use the y's. + @param dist a distance from 0 to 1 spanning the whole curve. + @param c0 the x (resp. y) coordinate of the start point. + @param c1 the x (resp. y) coordinate of the first control point. + @param c2 the x (resp. y) coordinate of the second control point. + @param c3 the x (resp. y) coordinate of the end point. + @result The x (resp. y) coordinate of the point at 'dist'. + */ +float bezierInterpolate (float dist, float c0, float c1, float c2, float c3); + +/*! + @brief Interpolate along a bezier curve to the given distance. + @param dist a distance from 0 to 1 spanning the whole curve. + @param c0 the x start point. + @param c1 the x first control point. + @param c2 the x second control point. + @param c3 the x end point. + @result The point at 'dist'. + */ +NSPoint bezierInterpolateFull (float dist, NSPoint c0, NSPoint c1, NSPoint c2, NSPoint c3); + +/*! + * @brief Find whether two line segments intersect + * @param l1start The starting point of line segment 1 + * @param l1end The ending point of line segment 1 + * @param l2start The starting point of line segment 2 + * @param l2end The ending point of line segment 2 + * @param result A location to store the intersection point + * @result YES if they intersect, NO if they do not + */ +BOOL lineSegmentsIntersect (NSPoint l1start, NSPoint l1end, NSPoint l2start, NSPoint l2end, NSPoint *result); + +/*! + * @brief Find whether a line segment intersects a bezier curve + * @detail Always finds the intersection furthest along the line segment + * @param lstart The starting point of the line segment + * @param lend The ending point of the line segment + * @param c0 The starting point of the bezier curve + * @param c1 The first control point of the bezier curve + * @param c2 The second control point of the bezier curve + * @param c3 The ending point of the bezier curve + * @param result A location to store the intersection point + * @result YES if they intersect, NO if they do not + */ +BOOL lineSegmentIntersectsBezier (NSPoint lstart, NSPoint lend, NSPoint c0, NSPoint c1, NSPoint c2, NSPoint c3, NSPoint *result); + +/*! + * @brief Find whether a line segment enters a rectangle + * @param lineStart The starting point of the line segment + * @param lineEnd The ending point of the line segment + * @param rect The rectangle + * @result YES if they intersect, NO if they do not + */ +BOOL lineSegmentIntersectsRect (NSPoint lineStart, NSPoint lineEnd, NSRect rect); + +/*! + * @brief Find where a ray exits a rectangle + * @param rayStart The starting point of the ray; must be contained in rect + * @param angle_rads The angle of the ray, in radians + * @param rect The rectangle + * @result The point at which the ray leaves the rect + */ +NSPoint findExitPointOfRay (NSPoint rayStart, float angle_rads, NSRect rect); + +/*! + @brief Round val to nearest stepSize + @param stepSize the courseness + @param val a value to round + */ +float roundToNearest(float stepSize, float val); + +/*! + @brief Convert radians into degrees + */ +float radiansToDegrees(float radians); + +/*! + @brief Convert degrees into radians + */ +float degreesToRadians(float degrees); + +/*! + @brief Normalises an angle (in degrees) to fall between -179 and 180 + */ +int normaliseAngleDeg (int degrees); + +/*! + @brief Normalises an angle (in radians) to fall in the range (-pi,pi] + */ +float normaliseAngleRad (float rads); + +/*! + @brief Express a byte as alpha-only hex, with digits (0..16) -> (a..jA..F) + @param sh A number 0-255 + @result A string 'aa'-'FF' + */ +NSString *alphaHex(unsigned short sh); + +const char *find_start_of_nth_line (const char * string, int line); + +/*! + @brief Formats a CGFloat as a string, removing trailing zeros + @detail Unlike formatting an NSNumber, or using %g, it will never + produce scientific notation (like "2.00e2"). Unlike %f, + it will not include unnecessary trailing zeros. + */ +NSString *formatFloat(CGFloat f, int maxdps); + diff --git a/tikzit-1/src/common/util.m b/tikzit-1/src/common/util.m new file mode 100644 index 0000000..e9b8899 --- /dev/null +++ b/tikzit-1/src/common/util.m @@ -0,0 +1,403 @@ +// +// util.m +// TikZiT +// +// Copyright 2010 Aleks Kissinger. All rights reserved. +// +// +// This file is part of TikZiT. +// +// TikZiT is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// TikZiT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// + +#import "util.h" +#import "math.h" + +static BOOL fuzzyCompare(float f1, float f2) { + return (ABS(f1 - f2) <= 0.00001f * MIN(ABS(f1), ABS(f2))); +} + +NSRect NSRectWithPoint(NSRect rect, NSPoint p) { + CGFloat minX = NSMinX(rect); + CGFloat maxX = NSMaxX(rect); + CGFloat minY = NSMinY(rect); + CGFloat maxY = NSMaxY(rect); + if (p.x < minX) { + minX = p.x; + } else if (p.x > maxX) { + maxX = p.x; + } + if (p.y < minY) { + minY = p.y; + } else if (p.y > maxY) { + maxY = p.y; + } + return NSMakeRect(minX, minY, maxX - minX, maxY - minY); +} + +NSRect NSRectAroundPointsWithPadding(NSPoint p1, NSPoint p2, float padding) { + return NSMakeRect(MIN(p1.x,p2.x)-padding, + MIN(p1.y,p2.y)-padding, + ABS(p2.x-p1.x)+(2.0f*padding), + ABS(p2.y-p1.y)+(2.0f*padding)); +} + +NSRect NSRectAroundPoints(NSPoint p1, NSPoint p2) { + return NSRectAroundPointsWithPadding(p1, p2, 0.0f); +} + +NSRect NSRectAround4PointsWithPadding(NSPoint p1, NSPoint p2, NSPoint p3, NSPoint p4, float padding) { + float leftMost = MIN(p1.x, p2.x); + leftMost = MIN(leftMost, p3.x); + leftMost = MIN(leftMost, p4.x); + float rightMost = MAX(p1.x, p2.x); + rightMost = MAX(rightMost, p3.x); + rightMost = MAX(rightMost, p4.x); + float topMost = MIN(p1.y, p2.y); + topMost = MIN(topMost, p3.y); + topMost = MIN(topMost, p4.y); + float bottomMost = MAX(p1.y, p2.y); + bottomMost = MAX(bottomMost, p3.y); + bottomMost = MAX(bottomMost, p4.y); + return NSMakeRect(leftMost-padding, + topMost-padding, + (rightMost - leftMost)+(2.0f*padding), + (bottomMost - topMost)+(2.0f*padding)); +} + +NSRect NSRectAround4Points(NSPoint p1, NSPoint p2, NSPoint p3, NSPoint p4) { + return NSRectAround4PointsWithPadding(p1, p2, p3, p4, 0.0f); +} + +float NSDistanceBetweenPoints(NSPoint p1, NSPoint p2) { + float dx = p2.x - p1.x; + float dy = p2.y - p1.y; + return sqrt(dx * dx + dy * dy); +} + +float good_atan(float dx, float dy) { + if (dx > 0) { + return atan(dy/dx); + } else if (dx < 0) { + return M_PI + atan(dy/dx); + } else { + if (dy > 0) return 0.5 * M_PI; + else if (dy < 0) return 1.5 * M_PI; + else return 0; + } +} + +// interpolate on a cubic bezier curve +float bezierInterpolate(float dist, float c0, float c1, float c2, float c3) { + float distp = 1 - dist; + return (distp*distp*distp) * c0 + + 3 * (distp*distp) * dist * c1 + + 3 * (dist*dist) * distp * c2 + + (dist*dist*dist) * c3; +} + +NSPoint bezierInterpolateFull (float dist, NSPoint c0, NSPoint c1, NSPoint c2, NSPoint c3) { + return NSMakePoint (bezierInterpolate (dist, c0.x, c1.x, c2.x, c3.x), + bezierInterpolate (dist, c0.y, c1.y, c2.y, c3.y)); +} + +static void lineCoeffsFromPoints(NSPoint p1, NSPoint p2, float *A, float *B, float *C) { + *A = p2.y - p1.y; + *B = p1.x - p2.x; + *C = (*A) * p1.x + (*B) * p1.y; +} + +static void lineCoeffsFromPointAndAngle(NSPoint p, float angle, float *A, float *B, float *C) { + *A = sin (angle); + *B = -cos (angle); + *C = (*A) * p.x + (*B) * p.y; +} + +static BOOL lineSegmentContainsPoint(NSPoint l1, NSPoint l2, float x, float y) { + float minX = MIN(l1.x, l2.x); + float maxX = MAX(l1.x, l2.x); + float minY = MIN(l1.y, l2.y); + float maxY = MAX(l1.y, l2.y); + return (x >= minX || fuzzyCompare (x, minX)) && + (x <= maxX || fuzzyCompare (x, maxX)) && + (y >= minY || fuzzyCompare (y, minY)) && + (y <= maxY || fuzzyCompare (y, maxY)); +} + +BOOL lineSegmentsIntersect(NSPoint l1start, NSPoint l1end, NSPoint l2start, NSPoint l2end, NSPoint *result) { + // Ax + By = C + float A1, B1, C1; + lineCoeffsFromPoints(l1start, l1end, &A1, &B1, &C1); + float A2, B2, C2; + lineCoeffsFromPoints(l2start, l2end, &A2, &B2, &C2); + + float det = A1*B2 - A2*B1; + if (det == 0.0f) { + // parallel + return NO; + } else { + float x = (B2*C1 - B1*C2)/det; + float y = (A1*C2 - A2*C1)/det; + + if (lineSegmentContainsPoint(l1start, l1end, x, y) && + lineSegmentContainsPoint(l2start, l2end, x, y)) { + if (result) { + (*result).x = x; + (*result).y = y; + } + return YES; + } + } + return NO; +} + +BOOL lineSegmentIntersectsBezier (NSPoint lstart, NSPoint lend, NSPoint c0, NSPoint c1, NSPoint c2, NSPoint c3, NSPoint *result) { + NSRect curveBounds = NSRectAround4Points(c0, c1, c2, c3); + if (!lineSegmentIntersectsRect(lstart, lend, curveBounds)) + return NO; + + const int divisions = 20; + const float chunkSize = 1.0f/(float)divisions; + float chunkStart = 0.0f; + BOOL found = NO; + + for (int i = 0; i < divisions; ++i) { + float chunkEnd = chunkStart + chunkSize; + + NSPoint p1 = bezierInterpolateFull (chunkStart, c0, c1, c2, c3); + NSPoint p2 = bezierInterpolateFull (chunkEnd, c0, c1, c2, c3); + + NSPoint p; + if (lineSegmentsIntersect (lstart, lend, p1, p2, &p)) { + lstart = p; + found = YES; + } + + chunkStart = chunkEnd; + } + if (found && result) { + *result = lstart; + } + return found; +} + +BOOL lineSegmentIntersectsRect(NSPoint lineStart, NSPoint lineEnd, NSRect rect) { + const float rectMaxX = NSMaxX(rect); + const float rectMinX = NSMinX(rect); + const float rectMaxY = NSMaxY(rect); + const float rectMinY = NSMinY(rect); + + // check if the segment is entirely to one side of the rect + if (lineStart.x > rectMaxX && lineEnd.x > rectMaxX) { + return NO; + } + if (lineStart.x < rectMinX && lineEnd.x < rectMinX) { + return NO; + } + if (lineStart.y > rectMaxY && lineEnd.y > rectMaxY) { + return NO; + } + if (lineStart.y < rectMinY && lineEnd.y < rectMinY) { + return NO; + } + + // Now check whether the (infinite) line intersects the rect + // (if it does, so does the segment, due to above checks) + + // Ax + By = C + float A, B, C; + lineCoeffsFromPoints(lineStart, lineEnd, &A, &B, &C); + + const float tlVal = A * rectMinX + B * rectMaxY - C; + const float trVal = A * rectMaxX + B * rectMaxY - C; + const float blVal = A * rectMinX + B * rectMinY - C; + const float brVal = A * rectMaxX + B * rectMinY - C; + + if (tlVal < 0 && trVal < 0 && blVal < 0 && brVal < 0) { + // rect below line + return NO; + } + if (tlVal > 0 && trVal > 0 && blVal > 0 && brVal > 0) { + // rect above line + return NO; + } + + return YES; +} + +NSPoint findExitPointOfRay (NSPoint p, float angle_rads, NSRect rect) { + const float rectMinX = NSMinX (rect); + const float rectMaxX = NSMaxX (rect); + const float rectMinY = NSMinY (rect); + const float rectMaxY = NSMaxY (rect); + + const float angle = normaliseAngleRad (angle_rads); + + // special case the edges + if (p.y == rectMaxY && angle > 0 && angle < M_PI) { + // along the top of the box + return p; + } + if (p.y == rectMinY && angle < 0 && angle > -M_PI) { + // along the bottom of the box + return p; + } + if (p.x == rectMaxX && angle > -M_PI/2.0f && angle < M_PI/2.0f) { + // along the right of the box + return p; + } + if (p.x == rectMinX && (angle > M_PI/2.0f || angle < -M_PI/2.0f)) { + // along the left of the box + return p; + } + + float A1, B1, C1; + lineCoeffsFromPointAndAngle(p, angle, &A1, &B1, &C1); + //NSLog(@"Ray is %fx + %fy = %f", A1, B1, C1); + + const float tlAngle = normaliseAngleRad (good_atan (rectMinX - p.x, rectMaxY - p.y)); + const float trAngle = normaliseAngleRad (good_atan (rectMaxX - p.x, rectMaxY - p.y)); + if (angle <= tlAngle && angle >= trAngle) { + // exit top + float A2, B2, C2; + lineCoeffsFromPoints(NSMakePoint (rectMinX, rectMaxY), + NSMakePoint (rectMaxX, rectMaxY), + &A2, &B2, &C2); + float det = A1*B2 - A2*B1; + NSCAssert(det != 0.0f, @"Parallel lines?"); + NSPoint intersect = NSMakePoint ((B2*C1 - B1*C2)/det, + (A1*C2 - A2*C1)/det); + return intersect; + } + + const float brAngle = normaliseAngleRad (good_atan (rectMaxX - p.x, rectMinY - p.y)); + if (angle <= trAngle && angle >= brAngle) { + // exit right + float A2, B2, C2; + lineCoeffsFromPoints(NSMakePoint (rectMaxX, rectMaxY), + NSMakePoint (rectMaxX, rectMinY), + &A2, &B2, &C2); + //NSLog(@"Edge is %fx + %fy = %f", A2, B2, C2); + float det = A1*B2 - A2*B1; + NSCAssert(det != 0.0f, @"Parallel lines?"); + NSPoint intersect = NSMakePoint ((B2*C1 - B1*C2)/det, + (A1*C2 - A2*C1)/det); + return intersect; + } + + const float blAngle = normaliseAngleRad (good_atan (rectMinX - p.x, rectMinY - p.y)); + if (angle <= brAngle && angle >= blAngle) { + // exit bottom + float A2, B2, C2; + lineCoeffsFromPoints(NSMakePoint (rectMaxX, rectMinY), + NSMakePoint (rectMinX, rectMinY), + &A2, &B2, &C2); + float det = A1*B2 - A2*B1; + NSCAssert(det != 0.0f, @"Parallel lines?"); + NSPoint intersect = NSMakePoint ((B2*C1 - B1*C2)/det, + (A1*C2 - A2*C1)/det); + return intersect; + } else { + // exit left + float A2, B2, C2; + lineCoeffsFromPoints(NSMakePoint (rectMinX, rectMaxY), + NSMakePoint (rectMinX, rectMinY), + &A2, &B2, &C2); + float det = A1*B2 - A2*B1; + NSCAssert(det != 0.0f, @"Parallel lines?"); + NSPoint intersect = NSMakePoint ((B2*C1 - B1*C2)/det, + (A1*C2 - A2*C1)/det); + return intersect; + } +} + +float roundToNearest(float stepSize, float val) { + if (stepSize==0.0f) return val; + else return round(val/stepSize)*stepSize; +} + +float radiansToDegrees (float radians) { + return (radians * 180.0f) / M_PI; +} + +float degreesToRadians(float degrees) { + return (degrees * M_PI) / 180.0f; +} + +int normaliseAngleDeg (int degrees) { + while (degrees > 180) { + degrees -= 360; + } + while (degrees <= -180) { + degrees += 360; + } + return degrees; +} + +float normaliseAngleRad (float rads) { + while (rads > M_PI) { + rads -= 2 * M_PI; + } + while (rads <= -M_PI) { + rads += 2 * M_PI; + } + return rads; +} + +static char ahex[] = +{'a','b','c','d','e','f','g','h','i','j', + 'A','B','C','D','E','F'}; + +NSString *alphaHex(unsigned short sh) { + if (sh > 255) return @"!!"; + return [NSString stringWithFormat:@"%c%c", ahex[sh/16], ahex[sh%16]]; +} + +const char *find_start_of_nth_line (const char * string, int line) { + int l = 0; + const char *lineStart = string; + while (*lineStart && l < line) { + while (*lineStart && *lineStart != '\n') { + ++lineStart; + } + if (*lineStart) { + ++l; + ++lineStart; + } + } + return lineStart; +} + +NSString *formatFloat(CGFloat f, int maxdps) { + NSMutableString *result = [NSMutableString + stringWithFormat:@"%.*f", maxdps, f]; + // delete trailing zeros + NSUInteger lastPos = [result length] - 1; + NSUInteger firstDigit = ([result characterAtIndex:0] == '-') ? 1 : 0; + while (lastPos > firstDigit) { + if ([result characterAtIndex:lastPos] == '0') { + [result deleteCharactersInRange:NSMakeRange(lastPos, 1)]; + lastPos -= 1; + } else { + break; + } + } + if ([result characterAtIndex:lastPos] == '.') { + [result deleteCharactersInRange:NSMakeRange(lastPos, 1)]; + lastPos -= 1; + } + if ([@"-0" isEqualToString:result]) + return @"0"; + else + return result; +} + +// vi:ft=objc:noet:ts=4:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Application.h b/tikzit-1/src/gtk/Application.h new file mode 100644 index 0000000..1b48a79 --- /dev/null +++ b/tikzit-1/src/gtk/Application.h @@ -0,0 +1,155 @@ +/* + * Copyright 2012 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 "InputDelegate.h" + +@class Application; +@class Configuration; +@class ContextWindow; +@class Preambles; +@class PreambleEditor; +@class PreviewWindow; +@class SettingsDialog; +@class StyleManager; +@class TikzDocument; +@class ToolBox; +@class Window; +@protocol Tool; + +extern Application* app; + +/** + * Manages the main application window + */ +@interface Application: NSObject { + // the main application configuration + Configuration *configFile; + // maintains the known (user-defined) styles + StyleManager *styleManager; + // maintains the preambles used for previews + Preambles *preambles; + // the last-accessed folders (for open and save dialogs) + NSString *lastOpenFolder; + NSString *lastSaveAsFolder; + + ToolBox *toolBox; + PreambleEditor *preambleWindow; + ContextWindow *contextWindow; + SettingsDialog *settingsDialog; + + // the open windows (array of Window*) + NSMutableArray *openWindows; + + // tools + id<Tool> activeTool; + NSArray *tools; +} + +/** + * The main application configuration file + */ +@property (readonly) Configuration *mainConfiguration; + +/** + * The app-wide style manager instance + */ +@property (readonly) StyleManager *styleManager; + +/** + * The app-wide preambles registry + */ +@property (readonly) Preambles *preambles; + +/** + * The tools + */ +@property (readonly) NSArray *tools; + +/** + * The currently-selected tool + */ +@property (assign) id<Tool> activeTool; + +/** + * The folder last actively chosen by a user for opening a file + */ +@property (copy) NSString *lastOpenFolder; + +/** + * The folder last actively chosen by a user for saving a file + */ +@property (copy) NSString *lastSaveAsFolder; + +/** + * The application instance. + */ ++ (Application*) app; + +/** + * Starts the application with a single window containing an empty file + */ +- (id) init; +/** + * Starts the application with the given files open + */ +- (id) initWithFiles:(NSArray*)files; + +/** + * Loads a new, empty document in a new window + */ +- (void) newWindow; +/** + * Loads an existing document from a file in a new window + * + * @param doc the document the new window should show + */ +- (void) newWindowWithDocument:(TikzDocument*)doc; +/** + * Quit the application, confirming with the user if there are + * changes to any open documents. + */ +- (void) quit; + +/** + * Show the dialog for editing preambles. + */ +- (void) presentPreamblesEditor; +/** + * Show the context-aware window + */ +- (void) presentContextWindow; +/** + * Show the settings dialog. + */ +- (void) presentSettingsDialog; + +/** + * Save the application configuration to permanent storage + * + * Should be called just before the application exits + */ +- (void) saveConfiguration; + +/** + * @result YES if key event was processed, NO otherwise + */ +- (BOOL) activateToolForKey:(unsigned int)keyVal withMask:(InputMask)mask; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Application.m b/tikzit-1/src/gtk/Application.m new file mode 100644 index 0000000..e9935bd --- /dev/null +++ b/tikzit-1/src/gtk/Application.m @@ -0,0 +1,377 @@ +/* + * Copyright 2011-2012 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 "Application.h" + +#import "Configuration.h" +#import "EdgeStylesModel.h" +#import "NodeStylesModel.h" +#import "PreambleEditor.h" +#import "ContextWindow.h" +#import "Shape.h" +#import "SettingsDialog.h" +#import "StyleManager.h" +#import "StyleManager+Storage.h" +#import "SupportDir.h" +#import "TikzDocument.h" +#import "ToolBox.h" +#import "Window.h" + +#ifdef HAVE_POPPLER +#import "Preambles.h" +#import "Preambles+Storage.h" +#endif + +#import "BoundingBoxTool.h" +#import "CreateNodeTool.h" +#import "CreateEdgeTool.h" +#import "HandTool.h" +#import "SelectTool.h" + +// used for args to g_mkdir_with_parents +#import "stat.h" + +Application* app = nil; + +@interface Application (Notifications) +- (void) windowClosed:(NSNotification*)notification; +- (void) windowGainedFocus:(NSNotification*)notification; +- (void) selectedToolChanged:(NSNotification*)notification; +- (void) windowDocumentChanged:(NSNotification*)n; +@end + +@interface Application (Private) +- (void) setActiveWindow:(Window*)window; +@end + +@implementation Application + +@synthesize mainConfiguration=configFile; +@synthesize styleManager, preambles; +@synthesize lastOpenFolder, lastSaveAsFolder; +@synthesize tools; + ++ (Application*) app { + if (app == nil) { + [[[self alloc] init] release]; + } + return app; +} + +- (id) _initCommon { + if (app != nil) { + [self release]; + self = app; + return self; + } + self = [super init]; + + if (self) { + NSError *error = nil; + configFile = [[Configuration alloc] initWithName:@"tikzit" loadError:&error]; + if (error != nil) { + logError (error, @"WARNING: Failed to load configuration"); + } + + styleManager = [[StyleManager alloc] init]; + [styleManager loadStylesUsingConfigurationName:@"styles"]; // FIXME: error message? + NodeStylesModel *nsm = [NodeStylesModel modelWithStyleManager:styleManager]; + EdgeStylesModel *esm = [EdgeStylesModel modelWithStyleManager:styleManager]; + +#ifdef HAVE_POPPLER + NSString *preamblesDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"preambles"]; + preambles = [[Preambles alloc] initFromDirectory:preamblesDir]; // FIXME: error message? + [preambles setStyleManager:styleManager]; + NSString *selectedPreamble = [configFile stringEntry:@"selectedPreamble" inGroup:@"Preambles"]; + if (selectedPreamble != nil) { + [preambles setSelectedPreambleName:selectedPreamble]; + } +#endif + + lastOpenFolder = [[configFile stringEntry:@"lastOpenFolder" inGroup:@"Paths"] retain]; + if (lastOpenFolder == nil) + lastOpenFolder = [[configFile stringEntry:@"lastFolder" inGroup:@"Paths"] retain]; + lastSaveAsFolder = [[configFile stringEntry:@"lastSaveAsFolder" inGroup:@"Paths"] retain]; + if (lastSaveAsFolder == nil) + lastSaveAsFolder = [[configFile stringEntry:@"lastFolder" inGroup:@"Paths"] retain]; + + openWindows = [[NSMutableArray alloc] init]; + + tools = [[NSArray alloc] initWithObjects: + [SelectTool tool], + [CreateNodeTool toolWithNodeStylesModel:nsm], + [CreateEdgeTool toolWithEdgeStylesModel:esm], + [BoundingBoxTool tool], + [HandTool tool], + nil]; + activeTool = [tools objectAtIndex:0]; + for (id<Tool> tool in tools) { + [tool loadConfiguration:configFile]; + } + + toolBox = [[ToolBox alloc] initWithTools:tools]; + [toolBox loadConfiguration:configFile]; + [toolBox setSelectedTool:activeTool]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(selectedToolChanged:) + name:@"ToolSelectionChanged" + object:toolBox]; + [toolBox show]; + + contextWindow = [[ContextWindow alloc] initWithNodeStylesModel:nsm + andEdgeStylesModel:esm]; + [contextWindow loadConfiguration:configFile]; + + app = [self retain]; + } + + return self; +} + +- (id) init { + self = [self _initCommon]; + + if (self) { + [self newWindow]; + } + + return self; +} + +- (id) initWithFiles:(NSArray*)files { + self = [self _initCommon]; + + if (self) { + int fileOpenCount = 0; + for (NSString *file in files) { + NSError *error = nil; + TikzDocument *doc = [TikzDocument documentFromFile:file styleManager:styleManager error:&error]; + if (doc != nil) { + [self newWindowWithDocument:doc]; + ++fileOpenCount; + } else { + logError(error, @"WARNING: failed to open file"); + } + } + if (fileOpenCount == 0) { + [self newWindow]; + } + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [configFile release]; + [styleManager release]; + [preambles release]; + [lastOpenFolder release]; + [lastSaveAsFolder release]; + [preambleWindow release]; + [settingsDialog release]; + [openWindows release]; + [tools release]; + [activeTool release]; + [toolBox release]; + [contextWindow release]; + + [super dealloc]; +} + +- (id<Tool>) activeTool { return activeTool; } +- (void) setActiveTool:(id<Tool>)tool { + if (activeTool == tool) + return; + + activeTool = tool; + [toolBox setSelectedTool:tool]; + for (Window* window in openWindows) { + [window setActiveTool:tool]; + } +} + +- (BOOL) activateToolForKey:(unsigned int)keyVal withMask:(InputMask)mask { + // FIXME: cache the accel info, rather than reparsing it every time? + for (id<Tool> tool in tools) { + guint toolKey = 0; + GdkModifierType toolMod = 0; + gtk_accelerator_parse ([[tool shortcut] UTF8String], &toolKey, &toolMod); + if (toolKey != 0 && toolKey == keyVal && (int)mask == (int)toolMod) { + [self setActiveTool:tool]; + return YES; + } + } + return NO; +} + +- (void) _addWindow:(Window*)window { + [window setActiveTool:activeTool]; + [openWindows addObject:window]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(windowClosed:) + name:@"WindowClosed" + object:window]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(windowGainedFocus:) + name:@"WindowGainedFocus" + object:window]; + if ([window hasFocus]) { + [self setActiveWindow:window]; + } +} + +- (void) newWindow { + [self _addWindow:[Window window]]; +} + +- (void) newWindowWithDocument:(TikzDocument*)doc { + [self _addWindow:[Window windowWithDocument:doc]]; +} + +- (void) quit { + NSMutableArray *unsavedDocs = [NSMutableArray arrayWithCapacity:[openWindows count]]; + for (Window *window in openWindows) { + TikzDocument *doc = [window document]; + if ([doc hasUnsavedChanges]) { + [unsavedDocs addObject:doc]; + } + } + if ([unsavedDocs count] > 0) { + // FIXME: show a dialog + return; + } + gtk_main_quit(); +} + +- (void) presentPreamblesEditor { +#ifdef HAVE_POPPLER + if (preambleWindow == nil) { + preambleWindow = [[PreambleEditor alloc] initWithPreambles:preambles]; + //[preambleWindow setParentWindow:mainWindow]; + } + [preambleWindow present]; +#endif +} + +- (void) presentContextWindow { + [contextWindow present]; +} + +- (void) presentSettingsDialog { + if (settingsDialog == nil) { + settingsDialog = [[SettingsDialog alloc] initWithConfiguration:configFile + andStyleManager:styleManager]; + //[settingsDialog setParentWindow:mainWindow]; + } + [settingsDialog present]; +} + +- (Configuration*) mainConfiguration { + return configFile; +} + +- (void) saveConfiguration { + NSError *error = nil; + +#ifdef HAVE_POPPLER + if (preambles != nil) { + NSString *preamblesDir = [[SupportDir userSupportDir] stringByAppendingPathComponent:@"preambles"]; + // NSFileManager is slightly dodgy on Windows + g_mkdir_with_parents ([preamblesDir UTF8String], S_IRUSR | S_IWUSR | S_IXUSR); + [preambles storeToDirectory:preamblesDir]; + [configFile setStringEntry:@"selectedPreamble" inGroup:@"Preambles" value:[preambles selectedPreambleName]]; + } +#endif + + [styleManager saveStylesUsingConfigurationName:@"styles"]; + + for (id<Tool> tool in tools) { + [tool saveConfiguration:configFile]; + } + [toolBox saveConfiguration:configFile]; + + [contextWindow saveConfiguration:configFile]; + + if (lastOpenFolder != nil) { + [configFile setStringEntry:@"lastOpenFolder" inGroup:@"Paths" value:lastOpenFolder]; + } + if (lastSaveAsFolder != nil) { + [configFile setStringEntry:@"lastSaveAsFolder" inGroup:@"Paths" value:lastSaveAsFolder]; + } + + if (![configFile writeToStoreWithError:&error]) { + logError (error, @"Could not write config file"); + } +} + +@end + +@implementation Application (Notifications) +- (void) windowClosed:(NSNotification*)notification { + Window *window = [notification object]; + [openWindows removeObjectIdenticalTo:window]; + [[NSNotificationCenter defaultCenter] removeObserver:self + name:nil + object:window]; + if ([openWindows count] == 0) { + gtk_main_quit(); + } else { + [self setActiveWindow:[openWindows objectAtIndex:0]]; + } +} + +- (void) windowGainedFocus:(NSNotification*)notification { + Window *window = [notification object]; + [self setActiveWindow:window]; +} + +- (void) selectedToolChanged:(NSNotification*)n { + id<Tool> tool = [[n userInfo] objectForKey:@"tool"]; + if (tool != nil) + [self setActiveTool:tool]; + else + NSLog(@"nil tool!"); +} + +- (void) windowDocumentChanged:(NSNotification*)n { + [contextWindow setDocument:[[n userInfo] objectForKey:@"document"]]; +} +@end + +@implementation Application (Private) +- (void) setActiveWindow:(Window*)window { + [[NSNotificationCenter defaultCenter] removeObserver:self + name:@"DocumentChanged" + object:nil]; + + [contextWindow setDocument:[window document]]; + + [contextWindow attachToWindow:window]; + [toolBox attachToWindow:window]; + + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(windowDocumentChanged:) + name:@"DocumentChanged" + object:window]; +} +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/BoundingBoxTool.h b/tikzit-1/src/gtk/BoundingBoxTool.h new file mode 100644 index 0000000..f6498b0 --- /dev/null +++ b/tikzit-1/src/gtk/BoundingBoxTool.h @@ -0,0 +1,45 @@ +/* + * 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 "TZFoundation.h" +#import "Tool.h" + +typedef enum { + NoHandle, + EastHandle, + SouthEastHandle, + SouthHandle, + SouthWestHandle, + WestHandle, + NorthWestHandle, + NorthHandle, + NorthEastHandle +} ResizeHandle; + +@interface BoundingBoxTool : NSObject <Tool> { + GraphRenderer *renderer; + NSPoint dragOrigin; + ResizeHandle currentResizeHandle; + BOOL drawingNewBox; +} + ++ (id) tool; +- (id) init; +@end + + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/BoundingBoxTool.m b/tikzit-1/src/gtk/BoundingBoxTool.m new file mode 100644 index 0000000..483705e --- /dev/null +++ b/tikzit-1/src/gtk/BoundingBoxTool.m @@ -0,0 +1,353 @@ +/* + * Copyright 2011-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 "BoundingBoxTool.h" + +#import "GraphRenderer.h" +#import "TikzDocument.h" +#import "tzstockitems.h" + +static const float handle_size = 8.0; +float sideHandleTop(NSRect bbox) { + return (NSMinY(bbox) + NSMaxY(bbox) - handle_size)/2.0f; +} +float tbHandleLeft(NSRect bbox) { + return (NSMinX(bbox) + NSMaxX(bbox) - handle_size)/2.0f; +} + +@interface BoundingBoxTool (Private) +- (NSRect) screenBoundingBox; +- (ResizeHandle) boundingBoxResizeHandleAt:(NSPoint)p; +- (NSRect) boundingBoxResizeHandleRect:(ResizeHandle)handle; +- (void) setResizeCursorForHandle:(ResizeHandle)handle; +@end + +@implementation BoundingBoxTool +- (NSString*) name { return @"Bounding Box"; } +- (const gchar*) stockId { return TIKZIT_STOCK_BOUNDING_BOX; } +- (NSString*) helpText { return @"Set the bounding box"; } +- (NSString*) shortcut { return @"b"; } + ++ (id) tool { + return [[[self alloc] init] autorelease]; +} + +- (id) init { + self = [super init]; + + if (self) { + currentResizeHandle = NoHandle; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [renderer release]; + + [super dealloc]; +} + +- (GraphRenderer*) activeRenderer { return renderer; } +- (void) setActiveRenderer:(GraphRenderer*)r { + if (r == renderer) + return; + + [[renderer surface] setCursor:NormalCursor]; + + [r retain]; + [renderer release]; + renderer = r; +} + +- (GtkWidget*) configurationWidget { return NULL; } + +- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + + dragOrigin = pos; + currentResizeHandle = [self boundingBoxResizeHandleAt:pos]; + [[renderer document] startChangeBoundingBox]; + if (currentResizeHandle == NoHandle) { + drawingNewBox = YES; + [[[renderer document] graph] setBoundingBox:NSZeroRect]; + } else { + drawingNewBox = NO; + } + [renderer invalidateGraph]; +} + +- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask { + if (!(buttons & LeftButton)) { + ResizeHandle handle = [self boundingBoxResizeHandleAt:pos]; + [self setResizeCursorForHandle:handle]; + return; + } + + Transformer *transformer = [renderer transformer]; + Grid *grid = [renderer grid]; + Graph *graph = [[renderer document] graph]; + + if (currentResizeHandle == 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 (currentResizeHandle == NorthWestHandle || + currentResizeHandle == NorthHandle || + currentResizeHandle == 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 (currentResizeHandle == SouthWestHandle || + currentResizeHandle == SouthHandle || + currentResizeHandle == SouthEastHandle) { + + float dy = p2.y - NSMaxY(bbox); + if (-dy < bbox.size.height) { + bbox.size.height += dy; + } else { + bbox.size.height = 0; + } + } + + if (currentResizeHandle == NorthWestHandle || + currentResizeHandle == WestHandle || + currentResizeHandle == 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 (currentResizeHandle == NorthEastHandle || + currentResizeHandle == EastHandle || + currentResizeHandle == 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]]; + } + [[renderer document] changeBoundingBoxCheckPoint]; + [renderer invalidateGraph]; +} + +- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + + [[renderer document] endChangeBoundingBox]; + drawingNewBox = NO; + [renderer invalidateGraph]; +} + +- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface { + if (!drawingNewBox && [[[renderer document] graph] hasBoundingBox]) { + [context saveState]; + + [context setAntialiasMode:AntialiasDisabled]; + [context setLineWidth:1.0]; + + [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) loadConfiguration:(Configuration*)config {} +- (void) saveConfiguration:(Configuration*)config {} +@end + +@implementation BoundingBoxTool (Private) +- (NSRect) screenBoundingBox { + Transformer *transformer = [[renderer surface] transformer]; + Graph *graph = [[renderer document] graph]; + return [transformer rectToScreen:[graph boundingBox]]; +} + +- (ResizeHandle) boundingBoxResizeHandleAt:(NSPoint)p { + NSRect bbox = [self screenBoundingBox]; + if (p.x >= NSMaxX(bbox)) { + if (p.x <= NSMaxX(bbox) + handle_size) { + if (p.y >= NSMaxY(bbox)) { + if (p.y <= NSMaxY(bbox) + handle_size) { + return SouthEastHandle; + } + } else if (p.y <= NSMinY(bbox)) { + if (p.y >= NSMinY(bbox) - handle_size) { + return NorthEastHandle; + } + } else { + float eastHandleTop = sideHandleTop(bbox); + if (p.y >= eastHandleTop && p.y <= (eastHandleTop + handle_size)) { + return EastHandle; + } + } + } + } else if (p.x <= NSMinX(bbox)) { + if (p.x >= NSMinX(bbox) - handle_size) { + if (p.y >= NSMaxY(bbox)) { + if (p.y <= NSMaxY(bbox) + handle_size) { + return SouthWestHandle; + } + } else if (p.y <= NSMinY(bbox)) { + if (p.y >= NSMinY(bbox) - handle_size) { + return NorthWestHandle; + } + } else { + float westHandleTop = sideHandleTop(bbox); + if (p.y >= westHandleTop && p.y <= (westHandleTop + handle_size)) { + return WestHandle; + } + } + } + } else if (p.y >= NSMaxY(bbox)) { + if (p.y <= NSMaxY(bbox) + handle_size) { + float southHandleLeft = tbHandleLeft(bbox); + if (p.x >= southHandleLeft && p.x <= (southHandleLeft + handle_size)) { + return SouthHandle; + } + } + } else if (p.y <= NSMinY(bbox)) { + if (p.y >= NSMinY(bbox) - handle_size) { + float northHandleLeft = tbHandleLeft(bbox); + if (p.x >= northHandleLeft && p.x <= (northHandleLeft + handle_size)) { + return NorthHandle; + } + } + } + return NoHandle; +} + +- (NSRect) boundingBoxResizeHandleRect:(ResizeHandle)handle { + Graph *graph = [[renderer document] graph]; + if (![graph hasBoundingBox]) { + return NSZeroRect; + } + NSRect bbox = [self screenBoundingBox]; + float x; + float y; + switch (handle) { + case NorthEastHandle: + case EastHandle: + case SouthEastHandle: + x = NSMaxX(bbox); + break; + case NorthWestHandle: + case WestHandle: + case SouthWestHandle: + x = NSMinX(bbox) - handle_size; + break; + case SouthHandle: + case NorthHandle: + x = tbHandleLeft(bbox); + break; + default: + return NSZeroRect; + } + switch (handle) { + case EastHandle: + case WestHandle: + y = sideHandleTop(bbox); + break; + case SouthEastHandle: + case SouthHandle: + case SouthWestHandle: + y = NSMaxY(bbox); + break; + case NorthEastHandle: + case NorthHandle: + case NorthWestHandle: + y = NSMinY(bbox) - handle_size; + break; + default: + return NSZeroRect; + } + return NSMakeRect(x, y, handle_size, handle_size); +} + +- (void) setResizeCursorForHandle:(ResizeHandle)handle { + if (handle != currentResizeHandle) { + currentResizeHandle = handle; + Cursor c = NormalCursor; + switch (handle) { + case EastHandle: + c = ResizeRightCursor; + break; + case SouthEastHandle: + c = ResizeBottomRightCursor; + break; + case SouthHandle: + c = ResizeBottomCursor; + break; + case SouthWestHandle: + c = ResizeBottomLeftCursor; + break; + case WestHandle: + c = ResizeLeftCursor; + break; + case NorthWestHandle: + c = ResizeTopLeftCursor; + break; + case NorthHandle: + c = ResizeTopCursor; + break; + case NorthEastHandle: + c = ResizeTopRightCursor; + break; + default: + c = NormalCursor; + break; + } + [[renderer surface] setCursor:c]; + } +} +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/CairoRenderContext.h b/tikzit-1/src/gtk/CairoRenderContext.h new file mode 100644 index 0000000..ac9c5ee --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/CairoRenderContext.m b/tikzit-1/src/gtk/CairoRenderContext.m new file mode 100644 index 0000000..77e10b5 --- /dev/null +++ b/tikzit-1/src/gtk/CairoRenderContext.m @@ -0,0 +1,344 @@ +/* + * 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]; + 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]; + 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-1/src/gtk/ColorRGB+Gtk.h b/tikzit-1/src/gtk/ColorRGB+Gtk.h new file mode 100644 index 0000000..5cfb4d7 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/ColorRGB+Gtk.m b/tikzit-1/src/gtk/ColorRGB+Gtk.m new file mode 100644 index 0000000..be5dd56 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/ColorRGB+IntegerListStorage.h b/tikzit-1/src/gtk/ColorRGB+IntegerListStorage.h new file mode 100644 index 0000000..118eaee --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/ColorRGB+IntegerListStorage.m b/tikzit-1/src/gtk/ColorRGB+IntegerListStorage.m new file mode 100644 index 0000000..0103a3c --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/Configuration.h b/tikzit-1/src/gtk/Configuration.h new file mode 100644 index 0000000..6c68681 --- /dev/null +++ b/tikzit-1/src/gtk/Configuration.h @@ -0,0 +1,447 @@ +// +// Configuration.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-1/src/gtk/Configuration.m b/tikzit-1/src/gtk/Configuration.m new file mode 100644 index 0000000..4a3ed79 --- /dev/null +++ b/tikzit-1/src/gtk/Configuration.m @@ -0,0 +1,450 @@ +// +// Configuration.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 arrayWithCapacity:length]; + for (int i = 0; i < length; ++i) { + [result addObject:[NSString stringWithUTF8String:list[i]]]; + } + g_strfreev (list); + 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 arrayWithCapacity:length]; + for (int i = 0; i < length; ++i) { + [result addObject:[NSNumber numberWithBool:list[i]]]; + } + g_free (list); + 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 arrayWithCapacity:length]; + for (int i = 0; i < length; ++i) { + [result addObject:[NSNumber numberWithInt:list[i]]]; + } + g_free (list); + 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 arrayWithCapacity:length]; + for (int i = 0; i < length; ++i) { + [result addObject:[NSNumber numberWithDouble:list[i]]]; + } + g_free (list); + 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-1/src/gtk/ContextWindow.h b/tikzit-1/src/gtk/ContextWindow.h new file mode 100644 index 0000000..fcad9df --- /dev/null +++ b/tikzit-1/src/gtk/ContextWindow.h @@ -0,0 +1,53 @@ +/* + * Copyright 2011-2012 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 Configuration; +@class EdgeStylesModel; +@class NodeStylesModel; +@class PropertiesPane; +@class SelectionPane; +@class StyleManager; +@class TikzDocument; +@class Window; + +@interface ContextWindow: NSObject { + PropertiesPane *propsPane; + SelectionPane *selPane; + + GtkWidget *window; + GtkWidget *layout; +} + +@property (retain) TikzDocument *document; +@property (assign) BOOL visible; + +- (id) initWithStyleManager:(StyleManager*)mgr; +- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm + andEdgeStylesModel:(EdgeStylesModel*)esm; + +- (void) present; +- (void) attachToWindow:(Window*)parent; + +- (void) loadConfiguration:(Configuration*)config; +- (void) saveConfiguration:(Configuration*)config; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/ContextWindow.m b/tikzit-1/src/gtk/ContextWindow.m new file mode 100644 index 0000000..d8e9d20 --- /dev/null +++ b/tikzit-1/src/gtk/ContextWindow.m @@ -0,0 +1,169 @@ +/* + * Copyright 2011-2012 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 "ContextWindow.h" + +#import "Configuration.h" +#import "EdgeStylesModel.h" +#import "NodeStylesModel.h" +#import "PropertiesPane.h" +#import "SelectionPane.h" +#import "StyleManager.h" +#import "Window.h" + +#import "gtkhelpers.h" + +static gboolean props_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, ContextWindow *window); + +@implementation ContextWindow + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)sm { + return [self initWithNodeStylesModel:[NodeStylesModel modelWithStyleManager:sm] + andEdgeStylesModel:[EdgeStylesModel modelWithStyleManager:sm]]; +} + +- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm + andEdgeStylesModel:(EdgeStylesModel*)esm { + self = [super init]; + + if (self) { + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + g_object_ref_sink (window); + gtk_window_set_title (GTK_WINDOW (window), "Context"); + gtk_window_set_role (GTK_WINDOW (window), "context"); + gtk_window_set_type_hint (GTK_WINDOW (window), + GDK_WINDOW_TYPE_HINT_UTILITY); + gtk_window_set_default_size (GTK_WINDOW (window), 200, 500); + g_signal_connect (G_OBJECT (window), + "delete-event", + G_CALLBACK (props_window_delete_event_cb), + self); + + layout = gtk_vbox_new (FALSE, 3); + g_object_ref_sink (layout); + gtk_widget_show (layout); + gtk_container_set_border_width (GTK_CONTAINER (layout), 6); + + gtk_container_add (GTK_CONTAINER (window), layout); + + propsPane = [[PropertiesPane alloc] init]; + gtk_box_pack_start (GTK_BOX (layout), [propsPane gtkWidget], + TRUE, TRUE, 0); + + GtkWidget *sep = gtk_hseparator_new (); + gtk_widget_show (sep); + gtk_box_pack_start (GTK_BOX (layout), sep, + FALSE, FALSE, 0); + + selPane = [[SelectionPane alloc] initWithNodeStylesModel:nsm + andEdgeStylesModel:esm]; + gtk_box_pack_start (GTK_BOX (layout), [selPane gtkWidget], + FALSE, FALSE, 0); + + // hack to position the context window somewhere sensible + // (upper right) + gtk_window_parse_geometry (GTK_WINDOW (window), "-0+0"); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + g_object_unref (layout); + g_object_unref (window); + + [propsPane release]; + + [super dealloc]; +} + +- (TikzDocument*) document { + return [propsPane document]; +} + +- (void) setDocument:(TikzDocument*)doc { + [propsPane setDocument:doc]; + [selPane setDocument:doc]; +} + +- (BOOL) visible { + return gtk_widget_get_visible (window); +} + +- (void) setVisible:(BOOL)visible { + gtk_widget_set_visible (window, visible); +} + +- (void) present { + gtk_window_present (GTK_WINDOW (window)); +} + +- (void) attachToWindow:(Window*)parent { + utility_window_attach (GTK_WINDOW (window), [parent gtkWindow]); +} + +- (void) loadConfiguration:(Configuration*)config { + [propsPane loadConfiguration:config]; + [selPane loadConfiguration:config]; + + if ([config hasGroup:@"ContextWindow"]) { + tz_restore_window (GTK_WINDOW (window), + [config integerEntry:@"x" inGroup:@"ContextWindow"], + [config integerEntry:@"y" inGroup:@"ContextWindow"], + [config integerEntry:@"w" inGroup:@"ContextWindow"], + [config integerEntry:@"h" inGroup:@"ContextWindow"]); + } + [self setVisible:[config booleanEntry:@"visible" + inGroup:@"ContextWindow" + withDefault:YES]]; +} + +- (void) saveConfiguration:(Configuration*)config { + gint x, y, w, h; + + gtk_window_get_position (GTK_WINDOW (window), &x, &y); + gtk_window_get_size (GTK_WINDOW (window), &w, &h); + + [config setIntegerEntry:@"x" inGroup:@"ContextWindow" value:x]; + [config setIntegerEntry:@"y" inGroup:@"ContextWindow" value:y]; + [config setIntegerEntry:@"w" inGroup:@"ContextWindow" value:w]; + [config setIntegerEntry:@"h" inGroup:@"ContextWindow" value:h]; + [config setBooleanEntry:@"visible" + inGroup:@"ContextWindow" + value:[self visible]]; + + [propsPane saveConfiguration:config]; + [selPane saveConfiguration:config]; +} + +@end + +static gboolean props_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, ContextWindow *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window setVisible:NO]; + [pool drain]; + return TRUE; +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/CreateEdgeTool.h b/tikzit-1/src/gtk/CreateEdgeTool.h new file mode 100644 index 0000000..d33efce --- /dev/null +++ b/tikzit-1/src/gtk/CreateEdgeTool.h @@ -0,0 +1,45 @@ +/* + * 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 "TZFoundation.h" +#import "Tool.h" + +@class EdgeStyle; +@class EdgeStylesModel; +@class EdgeStyleSelector; +@class Node; +@class StyleManager; + +@interface CreateEdgeTool : NSObject <Tool> { + GraphRenderer *renderer; + EdgeStyleSelector *stylePicker; + GtkWidget *configWidget; + Node *sourceNode; + NSPoint sourceNodeScreenPoint; + NSPoint halfEdgeEnd; +} + +@property (retain) EdgeStyle *activeStyle; + ++ (id) toolWithStyleManager:(StyleManager*)sm; +- (id) initWithStyleManager:(StyleManager*)sm; ++ (id) toolWithEdgeStylesModel:(EdgeStylesModel*)esm; +- (id) initWithEdgeStylesModel:(EdgeStylesModel*)esm; +@end + + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/CreateEdgeTool.m b/tikzit-1/src/gtk/CreateEdgeTool.m new file mode 100644 index 0000000..f3fb2c0 --- /dev/null +++ b/tikzit-1/src/gtk/CreateEdgeTool.m @@ -0,0 +1,226 @@ +/* + * Copyright 2011-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 "CreateEdgeTool.h" + +#import "Configuration.h" +#import "EdgeStyleSelector.h" +#import "EdgeStylesModel.h" +#import "GraphRenderer.h" +#import "TikzDocument.h" +#import "tzstockitems.h" + +static void clear_style_button_cb (GtkButton *widget, + EdgeStyleSelector *selector); + +@implementation CreateEdgeTool +- (NSString*) name { return @"Create Edge"; } +- (const gchar*) stockId { return TIKZIT_STOCK_CREATE_EDGE; } +- (NSString*) helpText { return @"Create new edges"; } +- (NSString*) shortcut { return @"e"; } +@synthesize activeRenderer=renderer; +@synthesize configurationWidget=configWidget; + ++ (id) toolWithStyleManager:(StyleManager*)sm { + return [[[self alloc] initWithStyleManager:sm] autorelease]; +} + ++ (id) toolWithEdgeStylesModel:(EdgeStylesModel*)esm { + return [[[self alloc] initWithEdgeStylesModel:esm] autorelease]; +} + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)sm { + return [self initWithEdgeStylesModel:[EdgeStylesModel modelWithStyleManager:sm]]; +} + +- (id) initWithEdgeStylesModel:(EdgeStylesModel*)esm { + self = [super init]; + + if (self) { + stylePicker = [[EdgeStyleSelector alloc] initWithModel:esm]; + + configWidget = gtk_vbox_new (FALSE, 0); + g_object_ref_sink (configWidget); + + GtkWidget *label = gtk_label_new ("Edge style:"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (configWidget), + label, + FALSE, + FALSE, + 0); + + GtkWidget *selWindow = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (selWindow); + gtk_container_add (GTK_CONTAINER (selWindow), + [stylePicker widget]); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (selWindow), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_widget_show ([stylePicker widget]); + + GtkWidget *selectorFrame = gtk_frame_new (NULL); + gtk_widget_show (selectorFrame); + gtk_box_pack_start (GTK_BOX (configWidget), + selectorFrame, + TRUE, + TRUE, + 0); + gtk_container_add (GTK_CONTAINER (selectorFrame), + selWindow); + + GtkWidget *button = gtk_button_new_with_label ("No style"); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (configWidget), + button, + FALSE, + FALSE, + 0); + g_signal_connect (G_OBJECT (button), + "clicked", + G_CALLBACK (clear_style_button_cb), + stylePicker); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [renderer release]; + [stylePicker release]; + [sourceNode release]; + + g_object_unref (G_OBJECT (configWidget)); + + [super dealloc]; +} + +- (EdgeStyle*) activeStyle { + return [stylePicker selectedStyle]; +} + +- (void) setActiveStyle:(EdgeStyle*)style { + return [stylePicker setSelectedStyle:style]; +} + +- (void) invalidateHalfEdge { + NSRect invRect = NSRectAroundPoints(sourceNodeScreenPoint, halfEdgeEnd); + [renderer invalidateRect:NSInsetRect (invRect, -2.0f, -2.0f)]; +} + +- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + + sourceNode = [renderer anyNodeAt:pos]; + if (sourceNode != nil) { + Transformer *transformer = [[renderer surface] transformer]; + sourceNodeScreenPoint = [transformer toScreen:[sourceNode point]]; + halfEdgeEnd = pos; + [renderer setNode:sourceNode highlighted:YES]; + } +} + +- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask { + if (!(buttons & LeftButton)) + return; + if (sourceNode == nil) + return; + + [self invalidateHalfEdge]; + + [renderer clearHighlightedNodes]; + [renderer setNode:sourceNode highlighted:YES]; + halfEdgeEnd = pos; + Node *targ = [renderer anyNodeAt:pos]; + if (targ != nil) { + [renderer setNode:targ highlighted:YES]; + } + + [self invalidateHalfEdge]; +} + +- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + if (sourceNode == nil) + return; + + [renderer clearHighlightedNodes]; + [self invalidateHalfEdge]; + + Node *targ = [renderer anyNodeAt:pos]; + if (targ != nil) { + Edge *edge = [Edge edgeWithSource:sourceNode andTarget:targ]; + [edge setStyle:[self activeStyle]]; + [[renderer document] addEdge:edge]; + [renderer invalidateEdge:edge]; + } + + sourceNode = nil; +} + +- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface { + if (sourceNode == nil) { + return; + } + [context saveState]; + + [context setLineWidth:1.0]; + [context startPath]; + [context moveTo:sourceNodeScreenPoint]; + [context lineTo:halfEdgeEnd]; + [context strokePathWithColor:MakeRColor (0, 0, 0, 0.5)]; + + [context restoreState]; +} + +- (StyleManager*) styleManager { + return [[stylePicker model] styleManager]; +} + +- (void) loadConfiguration:(Configuration*)config { + NSString *styleName = [config stringEntry:@"ActiveStyle" + inGroup:@"CreateEdgeTool" + withDefault:nil]; + [self setActiveStyle:[[self styleManager] edgeStyleForName:styleName]]; +} + +- (void) saveConfiguration:(Configuration*)config { + [config setStringEntry:@"ActiveStyle" + inGroup:@"CreateEdgeTool" + value:[[self activeStyle] name]]; +} +@end + +static void clear_style_button_cb (GtkButton *widget, + EdgeStyleSelector *selector) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [selector setSelectedStyle:nil]; + [pool drain]; +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/CreateNodeTool.h b/tikzit-1/src/gtk/CreateNodeTool.h new file mode 100644 index 0000000..94d6b31 --- /dev/null +++ b/tikzit-1/src/gtk/CreateNodeTool.h @@ -0,0 +1,42 @@ +/* + * 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 "TZFoundation.h" +#import <gtk/gtk.h> +#import "Tool.h" + +@class NodeStyle; +@class NodeStyleSelector; +@class NodeStylesModel; +@class StyleManager; + +@interface CreateNodeTool : NSObject <Tool> { + GraphRenderer *renderer; + NodeStyleSelector *stylePicker; + GtkWidget *configWidget; +} + +@property (retain) NodeStyle *activeStyle; + ++ (id) toolWithStyleManager:(StyleManager*)sm; +- (id) initWithStyleManager:(StyleManager*)sm; ++ (id) toolWithNodeStylesModel:(NodeStylesModel*)nsm; +- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm; +@end + + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/CreateNodeTool.m b/tikzit-1/src/gtk/CreateNodeTool.m new file mode 100644 index 0000000..77b24f0 --- /dev/null +++ b/tikzit-1/src/gtk/CreateNodeTool.m @@ -0,0 +1,169 @@ +/* + * Copyright 2011-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 "CreateNodeTool.h" + +#import "Configuration.h" +#import "GraphRenderer.h" +#import "NodeStyleSelector.h" +#import "NodeStylesModel.h" +#import "TikzDocument.h" +#import "tzstockitems.h" + +static void clear_style_button_cb (GtkButton *widget, + NodeStyleSelector *selector); + +@implementation CreateNodeTool +- (NSString*) name { return @"Create Node"; } +- (const gchar*) stockId { return TIKZIT_STOCK_CREATE_NODE; } +- (NSString*) helpText { return @"Create new nodes"; } +- (NSString*) shortcut { return @"n"; } +@synthesize activeRenderer=renderer; +@synthesize configurationWidget=configWidget; + ++ (id) toolWithStyleManager:(StyleManager*)sm { + return [[[self alloc] initWithStyleManager:sm] autorelease]; +} + ++ (id) toolWithNodeStylesModel:(NodeStylesModel*)nsm { + return [[[self alloc] initWithNodeStylesModel:nsm] autorelease]; +} + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)sm { + return [self initWithNodeStylesModel:[NodeStylesModel modelWithStyleManager:sm]]; +} + +- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm { + self = [super init]; + + if (self) { + stylePicker = [[NodeStyleSelector alloc] initWithModel:nsm]; + + configWidget = gtk_vbox_new (FALSE, 0); + g_object_ref_sink (configWidget); + + GtkWidget *label = gtk_label_new ("Node style:"); + gtk_widget_show (label); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (configWidget), + label, + FALSE, + FALSE, + 0); + + GtkWidget *selWindow = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (selWindow); + gtk_container_add (GTK_CONTAINER (selWindow), + [stylePicker widget]); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (selWindow), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_widget_show ([stylePicker widget]); + + GtkWidget *selectorFrame = gtk_frame_new (NULL); + gtk_widget_show (selectorFrame); + gtk_box_pack_start (GTK_BOX (configWidget), + selectorFrame, + TRUE, + TRUE, + 0); + gtk_container_add (GTK_CONTAINER (selectorFrame), + selWindow); + + GtkWidget *button = gtk_button_new_with_label ("No style"); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (configWidget), + button, + FALSE, + FALSE, + 0); + g_signal_connect (G_OBJECT (button), + "clicked", + G_CALLBACK (clear_style_button_cb), + stylePicker); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [renderer release]; + [stylePicker release]; + + g_object_unref (G_OBJECT (configWidget)); + + [super dealloc]; +} + +- (NodeStyle*) activeStyle { + return [stylePicker selectedStyle]; +} + +- (void) setActiveStyle:(NodeStyle*)style { + return [stylePicker setSelectedStyle:style]; +} + +// FIXME: create node on press, and drag it around? +- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {} + +- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + + Transformer *transformer = [renderer transformer]; + NSPoint nodePoint = [transformer fromScreen:[[renderer grid] snapScreenPoint:pos]]; + Node *node = [Node nodeWithPoint:nodePoint]; + [node setStyle:[self activeStyle]]; + [[renderer document] addNode:node]; +} + +- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface {} + +- (StyleManager*) styleManager { + return [[stylePicker model] styleManager]; +} + +- (void) loadConfiguration:(Configuration*)config { + NSString *styleName = [config stringEntry:@"ActiveStyle" + inGroup:@"CreateNodeTool" + withDefault:nil]; + [self setActiveStyle:[[self styleManager] nodeStyleForName:styleName]]; +} + +- (void) saveConfiguration:(Configuration*)config { + [config setStringEntry:@"ActiveStyle" + inGroup:@"CreateNodeTool" + value:[[self activeStyle] name]]; +} +@end + +static void clear_style_button_cb (GtkButton *widget, + NodeStyleSelector *selector) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [selector setSelectedStyle:nil]; + [pool drain]; +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/DocumentContext.h b/tikzit-1/src/gtk/DocumentContext.h new file mode 100644 index 0000000..e4c1065 --- /dev/null +++ b/tikzit-1/src/gtk/DocumentContext.h @@ -0,0 +1,27 @@ +/* + * 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 "TZFoundation.h" + +@class TikzDocument; + +@interface DocumentContext { + TikzDocument *document; + GraphRenderer *renderSurface; +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Edge+Render.h b/tikzit-1/src/gtk/Edge+Render.h new file mode 100644 index 0000000..e88b28a --- /dev/null +++ b/tikzit-1/src/gtk/Edge+Render.h @@ -0,0 +1,34 @@ +/* + * 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) renderControlsInContext:(id<RenderContext>)context withTransformer:(Transformer*)transformer; +- (void) renderBasicEdgeInContext:(id<RenderContext>)context withTransformer:(Transformer*)t selected:(BOOL)selected; +- (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-1/src/gtk/Edge+Render.m b/tikzit-1/src/gtk/Edge+Render.m new file mode 100644 index 0000000..3cc2a14 --- /dev/null +++ b/tikzit-1/src/gtk/Edge+Render.m @@ -0,0 +1,267 @@ +/* + * 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) controlDistance { + const float dx = (targ.x - src.x); + const float dy = (targ.y - src.y); + if (dx == 0 && dy == 0) { + return weight; + } else { + return NSDistanceBetweenPoints(src, targ) * weight; + } +} + +- (void) renderControlsInContext:(id<RenderContext>)context withTransformer:(Transformer*)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) renderArrowStrokePathInContext:(id<RenderContext>)context withTransformer:(Transformer*)transformer color:(RColor)color { + + if ([self style] != nil) { + switch ([[self style] headStyle]) { + case AH_None: + break; + case AH_Plain: + [context startPath]; + [context moveTo:[transformer toScreen:[self leftHeadNormal]]]; + [context lineTo:[transformer toScreen:head]]; + [context lineTo:[transformer toScreen:[self rightHeadNormal]]]; + [context strokePathWithColor:color]; + break; + case AH_Latex: + [context startPath]; + [context moveTo:[transformer toScreen:[self leftHeadNormal]]]; + [context lineTo:[transformer toScreen:head]]; + [context lineTo:[transformer toScreen:[self rightHeadNormal]]]; + [context closeSubPath]; + [context strokePathWithColor:color andFillWithColor:color]; + break; + } + switch ([[self style] tailStyle]) { + case AH_None: + break; + case AH_Plain: + [context startPath]; + [context moveTo:[transformer toScreen:[self leftTailNormal]]]; + [context lineTo:[transformer toScreen:tail]]; + [context lineTo:[transformer toScreen:[self rightTailNormal]]]; + [context strokePathWithColor:color]; + break; + case AH_Latex: + [context startPath]; + [context moveTo:[transformer toScreen:[self leftTailNormal]]]; + [context lineTo:[transformer toScreen:tail]]; + [context lineTo:[transformer toScreen:[self rightTailNormal]]]; + [context closeSubPath]; + [context strokePathWithColor:color andFillWithColor:color]; + break; + } + } +} + +- (void) createStrokePathInContext:(id<RenderContext>)context withTransformer:(Transformer*)transformer { + NSPoint c_tail = [transformer toScreen:tail]; + NSPoint c_cp1 = [transformer toScreen:cp1]; + NSPoint c_cp2 = [transformer toScreen:cp2]; + NSPoint c_head = [transformer toScreen:head]; + + [context startPath]; + [context moveTo:c_tail]; + [context curveTo:c_head 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; + } + + } +} + +- (RColor) color { + if (style) { + return [[style colorRGB] rColor]; + } else { + return BlackRColor; + } +} + +- (void) renderBasicEdgeInContext:(id<RenderContext>)context withTransformer:(Transformer*)t selected:(BOOL)selected { + [self updateControls]; + [context saveState]; + + const CGFloat lineWidth = style ? [style thickness] : edgeWidth; + [context setLineWidth:lineWidth]; + RColor color = [self color]; + if (selected) { + color.alpha = 0.5; + } + + [self createStrokePathInContext:context withTransformer:t]; + [context strokePathWithColor:color]; + + [self renderArrowStrokePathInContext:context withTransformer:t color:color]; + + [context restoreState]; +} + +- (void) renderToSurface:(id <Surface>)surface withContext:(id<RenderContext>)context selected:(BOOL)selected { + [self renderBasicEdgeInContext:context withTransformer:[surface transformer] selected:selected]; + + if (selected) { + [self renderControlsInContext:context withTransformer:[surface transformer]]; + } + + if ([self hasEdgeNode]) { + NSPoint labelPt = [[surface transformer] toScreen:[self mid]]; + [[self edgeNode] renderLabelAt:labelPt + withContext:context]; + } +} + +- (NSRect) renderedBoundsWithTransformer:(Transformer*)t whenSelected:(BOOL)selected { + if (selected) { + float c_dist = [self controlDistance] + cpRadius; // include handle + NSRect cp1circ = NSMakeRect (head.x - c_dist, head.y - c_dist, 2*c_dist, 2*c_dist); + NSRect cp2circ = NSMakeRect (tail.x - c_dist, tail.y - c_dist, 2*c_dist, 2*c_dist); + NSRect rect = NSUnionRect ([self boundingRect], NSUnionRect (cp1circ, cp2circ)); + return [t rectToScreen:rect]; + } else { + return [t rectToScreen:[self boundingRect]]; + } +} + +- (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:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/EdgeStyle+Gtk.h b/tikzit-1/src/gtk/EdgeStyle+Gtk.h new file mode 100644 index 0000000..4323593 --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStyle+Gtk.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 <gtk/gtk.h> + +@interface EdgeStyle (Gtk) + +- (GdkColor) color; +- (void) setColor:(GdkColor)color; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/EdgeStyle+Gtk.m b/tikzit-1/src/gtk/EdgeStyle+Gtk.m new file mode 100644 index 0000000..886329e --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStyle+Gtk.m @@ -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 "EdgeStyle+Gtk.h" +#import "ColorRGB+Gtk.h" + +@implementation EdgeStyle (Gtk) + +- (GdkColor) color { + return [[self colorRGB] gdkColor]; +} + +- (void) setColor:(GdkColor)color { + [self setColorRGB:[ColorRGB colorWithGdkColor:color]]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/EdgeStyle+Storage.h b/tikzit-1/src/gtk/EdgeStyle+Storage.h new file mode 100644 index 0000000..74881f3 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/EdgeStyle+Storage.m b/tikzit-1/src/gtk/EdgeStyle+Storage.m new file mode 100644 index 0000000..45e2a20 --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStyle+Storage.m @@ -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 "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]; + [self setColorRGB: + [ColorRGB colorFromValueList: + [configFile integerListEntry:@"Color" + inGroup:groupName + withDefault:[colorRGB valueList]]]]; + } + + 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]; + [configFile setIntegerListEntry:@"Color" inGroup:groupName value:[[self colorRGB] valueList]]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/EdgeStyleEditor.h b/tikzit-1/src/gtk/EdgeStyleEditor.h new file mode 100644 index 0000000..2224bbb --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStyleEditor.h @@ -0,0 +1,45 @@ +/* + * Copyright 2012 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 EdgeStyle; + +@interface EdgeStyleEditor: NSObject { + EdgeStyle *style; + GtkTable *table; + GtkEntry *nameEdit; + GtkComboBox *decorationCombo; + GtkComboBox *headArrowCombo; + GtkComboBox *tailArrowCombo; + GtkColorButton *colorButton; + GtkWidget *makeColorTexSafeButton; + GtkAdjustment *thicknessAdj; + BOOL blockSignals; +} + +@property (retain) EdgeStyle *style; +@property (readonly) GtkWidget *widget; + +- (id) init; + +- (void) selectNameField; + +@end + +// vim:ft=objc:ts=4:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/EdgeStyleEditor.m b/tikzit-1/src/gtk/EdgeStyleEditor.m new file mode 100644 index 0000000..c7ca8bd --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStyleEditor.m @@ -0,0 +1,499 @@ +/* + * Copyright 2012 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 "EdgeStyleEditor.h" + +#import "EdgeStyle.h" +#import "EdgeStyle+Gtk.h" +#import "Shape.h" + +#include <gdk-pixbuf/gdk-pixdata.h> + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-sign" +#import "edgedecdata.m" +#pragma GCC diagnostic pop + +// {{{ Data Types +enum { + DEC_NAME_COL = 0, + DEC_PREVIEW_COL, + DEC_VALUE_COL, + DEC_N_COLS +}; + +struct dec_info { + const gchar *name; + const GdkPixdata *pixdata; + int value; +}; +static struct dec_info ed_entries[] = { + { "None", &ED_none_pixdata, ED_None }, + { "Arrow", &ED_arrow_pixdata, ED_Arrow }, + { "Tick", &ED_tick_pixdata, ED_Tick } +}; +static guint n_ed_entries = G_N_ELEMENTS (ed_entries); +static struct dec_info ah_head_entries[] = { + { "None", &AH_none_pixdata, AH_None }, + { "Plain", &AH_plain_head_pixdata, AH_Plain }, + { "Latex", &AH_latex_head_pixdata, AH_Latex } +}; +static guint n_ah_head_entries = G_N_ELEMENTS (ah_head_entries); +static struct dec_info ah_tail_entries[] = { + { "None", &AH_none_pixdata, AH_None }, + { "Plain", &AH_plain_tail_pixdata, AH_Plain }, + { "Latex", &AH_latex_tail_pixdata, AH_Latex } +}; +static guint n_ah_tail_entries = G_N_ELEMENTS (ah_tail_entries); + +static const guint row_count = 6; + +// }}} +// {{{ Internal interfaces +// {{{ GTK+ Callbacks +static void style_name_edit_changed_cb (GtkEditable *widget, EdgeStyleEditor *editor); +static void decoration_combo_changed_cb (GtkComboBox *widget, EdgeStyleEditor *editor); +static void head_arrow_combo_changed_cb (GtkComboBox *widget, EdgeStyleEditor *editor); +static void tail_arrow_combo_changed_cb (GtkComboBox *widget, EdgeStyleEditor *editor); +static void thickness_adjustment_changed_cb (GtkAdjustment *widget, EdgeStyleEditor *editor); +static void color_changed_cb (GtkColorButton *widget, EdgeStyleEditor *editor); +static void make_color_safe_button_clicked_cb (GtkButton *widget, EdgeStyleEditor *editor); +// }}} +// {{{ Notifications + +@interface EdgeStyleEditor (Notifications) +- (void) nameChangedTo:(NSString*)value; +- (void) edgeDecorationChangedTo:(EdgeDectorationStyle)value; +- (void) headArrowChangedTo:(ArrowHeadStyle)value; +- (void) tailArrowChangedTo:(ArrowHeadStyle)value; +- (void) thicknessChangedTo:(double)value; +- (void) makeColorTexSafe; +- (void) colorChangedTo:(GdkColor)value; +@end + +// }}} +// {{{ Private + +@interface EdgeStyleEditor (Private) +- (void) load:(guint)count decorationStylesFrom:(struct dec_info*)info into:(GtkListStore*)list; +- (void) clearDecCombo:(GtkComboBox*)combo; +- (void) setDecCombo:(GtkComboBox*)combo toValue:(int)value; +@end + +// }}} +// }}} +// {{{ API + +@implementation EdgeStyleEditor + +- (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_misc_set_alignment (GTK_MISC (l), 0, 0.5); + 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 +} + +- (GtkComboBox*) _createDecComboWithEntries:(struct dec_info*)entries count:(guint)n { + GtkListStore *store = gtk_list_store_new (DEC_N_COLS, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_INT); + [self load:n decorationStylesFrom:entries into:store]; + + GtkComboBox *combo = GTK_COMBO_BOX (gtk_combo_box_new_with_model (GTK_TREE_MODEL (store))); + g_object_unref (store); + GtkCellRenderer *cellRend = gtk_cell_renderer_pixbuf_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), + cellRend, + TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (combo), cellRend, "pixbuf", DEC_PREVIEW_COL); + g_object_ref_sink (combo); + + return combo; +} + +- (GtkWidget*) _createMakeColorTexSafeButton { + 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 = @"The colour is not a predefined TeX colour.\nClick here to choose the nearest TeX-safe colour."; + 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_table_set_col_spacings (table, 6); + gtk_table_set_row_spacings (table, 6); + 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); + + + /** + * Edge decoration style + */ + decorationCombo = [self _createDecComboWithEntries:ed_entries count:n_ed_entries]; + [self _addWidget:GTK_WIDGET (decorationCombo) + withLabel:"Decoration" + atRow:1]; + g_signal_connect (G_OBJECT (decorationCombo), + "changed", + G_CALLBACK (decoration_combo_changed_cb), + self); + + + /** + * Head arrow style + */ + headArrowCombo = [self _createDecComboWithEntries:ah_head_entries count:n_ah_head_entries]; + [self _addWidget:GTK_WIDGET (headArrowCombo) + withLabel:"Arrow head" + atRow:2]; + g_signal_connect (G_OBJECT (headArrowCombo), + "changed", + G_CALLBACK (head_arrow_combo_changed_cb), + self); + + + /** + * Tail arrow style + */ + tailArrowCombo = [self _createDecComboWithEntries:ah_tail_entries count:n_ah_tail_entries]; + [self _addWidget:GTK_WIDGET (tailArrowCombo) + withLabel:"Arrow tail" + atRow:3]; + g_signal_connect (G_OBJECT (tailArrowCombo), + "changed", + G_CALLBACK (tail_arrow_combo_changed_cb), + self); + + + /** + * Colour + */ + GtkWidget *colorBox = gtk_hbox_new (FALSE, 0); + [self _addWidget:colorBox + withLabel:"Colour" + atRow:4]; + colorButton = GTK_COLOR_BUTTON (gtk_color_button_new ()); + g_object_ref_sink (colorButton); + gtk_widget_show (GTK_WIDGET (colorButton)); + gtk_box_pack_start (GTK_BOX (colorBox), GTK_WIDGET (colorButton), + FALSE, FALSE, 0); + makeColorTexSafeButton = [self _createMakeColorTexSafeButton]; + g_object_ref_sink (makeColorTexSafeButton); + gtk_box_pack_start (GTK_BOX (colorBox), makeColorTexSafeButton, + FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (colorButton), + "color-set", + G_CALLBACK (color_changed_cb), + self); + g_signal_connect (G_OBJECT (makeColorTexSafeButton), + "clicked", + G_CALLBACK (make_color_safe_button_clicked_cb), + self); + + + /** + * Thickness + */ + thicknessAdj = 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 (thicknessAdj); + GtkWidget *scaleSpin = gtk_spin_button_new (thicknessAdj, 0.0, 2); + [self _addWidget:scaleSpin + withLabel:"Thickness" + atRow:5]; + g_signal_connect (G_OBJECT (thicknessAdj), + "value-changed", + G_CALLBACK (thickness_adjustment_changed_cb), + self); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + g_object_unref (nameEdit); + g_object_unref (decorationCombo); + g_object_unref (colorButton); + g_object_unref (makeColorTexSafeButton); + g_object_unref (thicknessAdj); + g_object_unref (table); + [style release]; + + [super dealloc]; +} + +- (EdgeStyle*) style { + return style; +} + +- (void) setStyle:(EdgeStyle*)s { + blockSignals = YES; + EdgeStyle *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 setDecCombo:decorationCombo toValue:[style decorationStyle]]; + [self setDecCombo:headArrowCombo toValue:[style headStyle]]; + [self setDecCombo:tailArrowCombo toValue:[style tailStyle]]; + + GdkColor c = [style color]; + gtk_color_button_set_color(colorButton, &c); + gtk_widget_set_visible (makeColorTexSafeButton, ([[style colorRGB] name] == nil)); + + gtk_adjustment_set_value(thicknessAdj, [style thickness]); + } else { + gtk_entry_set_text(nameEdit, ""); + [self clearDecCombo:decorationCombo]; + [self clearDecCombo:headArrowCombo]; + [self clearDecCombo:tailArrowCombo]; + gtk_widget_set_visible (makeColorTexSafeButton, FALSE); + gtk_adjustment_set_value(thicknessAdj, 1.0); + gtk_widget_set_sensitive (GTK_WIDGET (table), FALSE); + } + + [oldStyle release]; + blockSignals = NO; +} + +- (GtkWidget*) widget { + return GTK_WIDGET (table); +} + +- (void) selectNameField { + gtk_widget_grab_focus (GTK_WIDGET (nameEdit)); + gtk_editable_select_region (GTK_EDITABLE (nameEdit), 0, -1); +} + +@end + +// }}} +// {{{ Notifications + +@implementation EdgeStyleEditor (Notifications) +- (void) nameChangedTo:(NSString*)value { + [style setName:value]; +} + +- (void) edgeDecorationChangedTo:(EdgeDectorationStyle)value { + [style setDecorationStyle:value]; +} + +- (void) headArrowChangedTo:(ArrowHeadStyle)value { + [style setHeadStyle:value]; +} + +- (void) tailArrowChangedTo:(ArrowHeadStyle)value { + [style setTailStyle:value]; +} + +- (void) thicknessChangedTo:(double)value { + [style setThickness:(float)value]; +} + +- (void) colorChangedTo:(GdkColor)value { + [style setColor:value]; + gtk_widget_set_visible (makeColorTexSafeButton, + [[style colorRGB] name] == nil); +} + +- (void) makeColorTexSafe { + if (style != nil) { + [[style colorRGB] setToClosestHashed]; + GdkColor color = [style color]; + gtk_color_button_set_color(colorButton, &color); + gtk_widget_set_visible (makeColorTexSafeButton, FALSE); + } +} +@end + +// }}} +// {{{ Private + +@implementation EdgeStyleEditor (Private) +- (BOOL) signalsBlocked { return blockSignals; } + +- (void) load:(guint)count decorationStylesFrom:(struct dec_info*)info into:(GtkListStore*)list { + GtkTreeIter iter; + + for (guint i = 0; i < count; ++i) { + GdkPixbuf *buf = gdk_pixbuf_from_pixdata (info[i].pixdata, FALSE, NULL); + gtk_list_store_append (list, &iter); + gtk_list_store_set (list, &iter, + DEC_NAME_COL, info[i].name, + DEC_PREVIEW_COL, buf, + DEC_VALUE_COL, info[i].value, + -1); + g_object_unref (buf); + } +} + +- (void) clearDecCombo:(GtkComboBox*)combo { + gtk_combo_box_set_active (combo, -1); +} + +- (void) setDecCombo:(GtkComboBox*)combo toValue:(int)value { + GtkTreeModel *model = gtk_combo_box_get_model (combo); + GtkTreeIter iter; + if (gtk_tree_model_get_iter_first (model, &iter)) { + do { + int rowValue; + gtk_tree_model_get (model, &iter, DEC_VALUE_COL, &rowValue, -1); + if (rowValue == value) { + gtk_combo_box_set_active_iter (combo, &iter); + return; + } + } while (gtk_tree_model_iter_next (model, &iter)); + } +} +@end + +// }}} +// {{{ GTK+ callbacks + +static void style_name_edit_changed_cb (GtkEditable *widget, EdgeStyleEditor *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 decoration_combo_changed_cb (GtkComboBox *widget, EdgeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + GtkTreeIter iter; + gtk_combo_box_get_active_iter (widget, &iter); + EdgeDectorationStyle dec = ED_None; + gtk_tree_model_get (gtk_combo_box_get_model (widget), &iter, DEC_VALUE_COL, &dec, -1); + [editor edgeDecorationChangedTo:dec]; + + [pool drain]; +} + +static void head_arrow_combo_changed_cb (GtkComboBox *widget, EdgeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + GtkTreeIter iter; + gtk_combo_box_get_active_iter (widget, &iter); + ArrowHeadStyle dec = AH_None; + gtk_tree_model_get (gtk_combo_box_get_model (widget), &iter, DEC_VALUE_COL, &dec, -1); + [editor headArrowChangedTo:dec]; + + [pool drain]; +} + +static void tail_arrow_combo_changed_cb (GtkComboBox *widget, EdgeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + GtkTreeIter iter; + gtk_combo_box_get_active_iter (widget, &iter); + ArrowHeadStyle dec = AH_None; + gtk_tree_model_get (gtk_combo_box_get_model (widget), &iter, DEC_VALUE_COL, &dec, -1); + [editor tailArrowChangedTo:dec]; + + [pool drain]; +} + +static void thickness_adjustment_changed_cb (GtkAdjustment *adj, EdgeStyleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor thicknessChangedTo:gtk_adjustment_get_value (adj)]; + [pool drain]; +} + +static void color_changed_cb (GtkColorButton *widget, EdgeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + GdkColor color; + gtk_color_button_get_color (widget, &color); + [editor colorChangedTo:color]; + + [pool drain]; +} + +static void make_color_safe_button_clicked_cb (GtkButton *widget, EdgeStyleEditor *editor) { + if ([editor signalsBlocked]) + return; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor makeColorTexSafe]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=4:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/EdgeStyleSelector.h b/tikzit-1/src/gtk/EdgeStyleSelector.h new file mode 100644 index 0000000..904bd93 --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStyleSelector.h @@ -0,0 +1,61 @@ +/* + * Copyright 2012 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 EdgeStyle; +@class EdgeStylesModel; +@class StyleManager; + +@interface EdgeStyleSelector: NSObject { + EdgeStylesModel *model; + GtkTreeView *view; +} + +/*! + @property widget + @brief The GTK widget + */ +@property (readonly) GtkWidget *widget; + +/*! + @property model + @brief The model to use. + */ +@property (retain) EdgeStylesModel *model; + +/*! + @property selectedStyle + @brief The selected style. + + When this changes, a SelectedStyleChanged notification will be posted + */ +@property (assign) EdgeStyle *selectedStyle; + +/*! + * Initialise with a new model for the given style manager + */ +- (id) initWithStyleManager:(StyleManager*)m; +/*! + * Initialise with the given model + */ +- (id) initWithModel:(EdgeStylesModel*)model; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/EdgeStyleSelector.m b/tikzit-1/src/gtk/EdgeStyleSelector.m new file mode 100644 index 0000000..6a9db33 --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStyleSelector.m @@ -0,0 +1,143 @@ +/* + * Copyright 2012 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 "EdgeStyleSelector.h" + +#import "EdgeStylesModel.h" + +// {{{ Internal interfaces +static void selection_changed_cb (GtkTreeSelection *sel, EdgeStyleSelector *mgr); +// }}} +// {{{ API + +@implementation EdgeStyleSelector + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)m { + return [self initWithModel:[EdgeStylesModel modelWithStyleManager:m]]; +} +- (id) initWithModel:(EdgeStylesModel*)m { + self = [super init]; + + if (self) { + model = [m retain]; + + view = GTK_TREE_VIEW (gtk_tree_view_new_with_model ([m model])); + gtk_tree_view_set_headers_visible (view, FALSE); + g_object_ref (view); + + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + renderer = gtk_cell_renderer_pixbuf_new (); + column = gtk_tree_view_column_new_with_attributes ( + "Preview", + renderer, + "pixbuf", EDGE_STYLES_ICON_COL, + NULL); + gtk_tree_view_append_column (view, column); + gtk_tree_view_set_tooltip_column (view, EDGE_STYLES_NAME_COL); + + GtkTreeSelection *sel = gtk_tree_view_get_selection (view); + gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE); + + g_signal_connect (G_OBJECT (sel), + "changed", + G_CALLBACK (selection_changed_cb), + self); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + g_object_unref (view); + [model release]; + + [super dealloc]; +} + +- (EdgeStylesModel*) model { + return model; +} + +- (void) setModel:(EdgeStylesModel*)m { + if (m == model) + return; + + EdgeStylesModel *oldModel = model; + model = [m retain]; + gtk_tree_view_set_model (view, [model model]); + [oldModel release]; +} + +- (GtkWidget*) widget { + return GTK_WIDGET (view); +} + +- (EdgeStyle*) selectedStyle { + GtkTreeSelection *sel = gtk_tree_view_get_selection (view); + GtkTreeIter iter; + + if (!gtk_tree_selection_get_selected (sel, NULL, &iter)) { + return nil; + } + + EdgeStyle *style = nil; + gtk_tree_model_get ([model model], &iter, EDGE_STYLES_PTR_COL, &style, -1); + + return style; +} + +- (void) setSelectedStyle:(EdgeStyle*)style { + GtkTreeSelection *sel = gtk_tree_view_get_selection (view); + + if (style == nil) { + gtk_tree_selection_unselect_all (sel); + return; + } + + GtkTreePath *path = [model pathFromStyle:style]; + if (path) { + gtk_tree_selection_unselect_all (sel); + gtk_tree_selection_select_path (sel, path); + gtk_tree_path_free (path); + } +} +@end + +// }}} +// {{{ GTK+ callbacks + +static void selection_changed_cb (GtkTreeSelection *sel, EdgeStyleSelector *mgr) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [[NSNotificationCenter defaultCenter] + postNotificationName:@"SelectedStyleChanged" + object:mgr]; + + [pool drain]; +} +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker + diff --git a/tikzit-1/src/gtk/EdgeStylesModel.h b/tikzit-1/src/gtk/EdgeStylesModel.h new file mode 100644 index 0000000..1166f92 --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStylesModel.h @@ -0,0 +1,62 @@ +/* + * Copyright 2011-2012 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 EdgeStyle; +@class StyleManager; + +enum { + EDGE_STYLES_NAME_COL = 0, + EDGE_STYLES_ICON_COL, + EDGE_STYLES_PTR_COL, + EDGE_STYLES_N_COLS +}; + +@interface EdgeStylesModel: NSObject { + GtkListStore *store; + StyleManager *styleManager; +} + +/*! + @property model + @brief The GTK+ tree model + */ +@property (readonly) GtkTreeModel *model; + +/*! + @property manager + @brief The StyleManager to use. + */ +@property (retain) StyleManager *styleManager; + +/*! + * Initialise with the given style manager + */ +- (id) initWithStyleManager:(StyleManager*)m; + ++ (id) modelWithStyleManager:(StyleManager*)m; + +- (EdgeStyle*) styleFromPath:(GtkTreePath*)path; +- (GtkTreePath*) pathFromStyle:(EdgeStyle*)style; +- (EdgeStyle*) styleFromIter:(GtkTreeIter*)iter; +- (GtkTreeIter*) iterFromStyle:(EdgeStyle*)style; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/EdgeStylesModel.m b/tikzit-1/src/gtk/EdgeStylesModel.m new file mode 100644 index 0000000..2de57ed --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStylesModel.m @@ -0,0 +1,367 @@ +/* + * Copyright 2012 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 "EdgeStylesModel.h" + +#import "CairoRenderContext.h" +#import "Edge.h" +#import "Edge+Render.h" +#import "EdgeStyle.h" +#import "Node.h" +#import "StyleManager.h" + +#import "gtkhelpers.h" + +#import <gdk-pixbuf/gdk-pixbuf.h> + +// {{{ Internal interfaces + +@interface EdgeStylesModel (Notifications) +- (void) edgeStylesReplaced:(NSNotification*)notification; +- (void) edgeStyleAdded:(NSNotification*)notification; +- (void) edgeStyleRemoved:(NSNotification*)notification; +- (void) observeValueForKeyPath:(NSString*)keyPath + ofObject:(id)object + change:(NSDictionary*)change + context:(void*)context; +@end + +@interface EdgeStylesModel (Private) +- (cairo_surface_t*) createEdgeIconSurface; +- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style; +- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style usingSurface:(cairo_surface_t*)surface; +- (void) addEdgeStyle:(EdgeStyle*)style; +- (void) addEdgeStyle:(EdgeStyle*)style usingSurface:(cairo_surface_t*)surface; +- (void) observeEdgeStyle:(EdgeStyle*)style; +- (void) stopObservingEdgeStyle:(EdgeStyle*)style; +- (void) clearEdgeStylesModel; +- (void) reloadEdgeStyles; +@end + +// }}} +// {{{ API + +@implementation EdgeStylesModel + ++ (id) modelWithStyleManager:(StyleManager*)m { + return [[[self alloc] initWithStyleManager:m] autorelease]; +} + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)m { + self = [super init]; + + if (self) { + store = gtk_list_store_new (EDGE_STYLES_N_COLS, + G_TYPE_STRING, + GDK_TYPE_PIXBUF, + G_TYPE_POINTER); + g_object_ref_sink (store); + + [self setStyleManager:m]; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [self clearEdgeStylesModel]; + g_object_unref (store); + [styleManager release]; + + [super dealloc]; +} + +- (GtkTreeModel*) model { + return GTK_TREE_MODEL (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(edgeStylesReplaced:) + name:@"EdgeStylesReplaced" + object:m]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(edgeStyleAdded:) + name:@"EdgeStyleAdded" + object:m]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(edgeStyleRemoved:) + name:@"EdgeStyleRemoved" + object:m]; + + [styleManager release]; + styleManager = m; + + [self reloadEdgeStyles]; +} + +- (EdgeStyle*) styleFromPath:(GtkTreePath*)path { + GtkTreeIter iter; + gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path); + EdgeStyle *style = nil; + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, EDGE_STYLES_PTR_COL, &style, -1); + return style; +} + +- (GtkTreePath*) pathFromStyle:(EdgeStyle*)style { + GtkTreeModel *m = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (m, &row)) { + do { + EdgeStyle *rowStyle; + gtk_tree_model_get (m, &row, EDGE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + return gtk_tree_model_get_path (m, &row); + } + } while (gtk_tree_model_iter_next (m, &row)); + } + return NULL; +} + +- (EdgeStyle*) styleFromIter:(GtkTreeIter*)iter { + EdgeStyle *style = nil; + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, EDGE_STYLES_PTR_COL, &style, -1); + return style; +} + +- (GtkTreeIter*) iterFromStyle:(EdgeStyle*)style { + GtkTreeModel *m = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (m, &row)) { + do { + EdgeStyle *rowStyle; + gtk_tree_model_get (m, &row, EDGE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + return gtk_tree_iter_copy (&row); + } + } while (gtk_tree_model_iter_next (m, &row)); + } + return NULL; +} +@end + +// }}} +// {{{ Notifications + +@implementation EdgeStylesModel (Notifications) + +- (void) edgeStylesReplaced:(NSNotification*)notification { + [self reloadEdgeStyles]; +} + +- (void) edgeStyleAdded:(NSNotification*)notification { + [self addEdgeStyle:[[notification userInfo] objectForKey:@"style"]]; +} + +- (void) edgeStyleRemoved:(NSNotification*)notification { + EdgeStyle *style = [[notification userInfo] objectForKey:@"style"]; + + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (model, &row)) { + do { + EdgeStyle *rowStyle; + gtk_tree_model_get (model, &row, EDGE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + gtk_list_store_remove (store, &row); + [self stopObservingEdgeStyle:rowStyle]; + [rowStyle release]; + return; + } + } while (gtk_tree_model_iter_next (model, &row)); + } +} + +- (void) observeValueForKeyPath:(NSString*)keyPath + ofObject:(id)object + change:(NSDictionary*)change + context:(void*)context +{ + if ([object class] != [EdgeStyle class]) + return; + + EdgeStyle *style = object; + + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (model, &row)) { + do { + EdgeStyle *rowStyle; + gtk_tree_model_get (model, &row, EDGE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + if ([@"name" isEqual:keyPath]) { + gtk_list_store_set (store, &row, EDGE_STYLES_NAME_COL, [[style name] UTF8String], -1); + } else { + GdkPixbuf *pixbuf = [self pixbufOfEdgeInStyle:style]; + gtk_list_store_set (store, &row, EDGE_STYLES_ICON_COL, pixbuf, -1); + g_object_unref (pixbuf); + } + } + } while (gtk_tree_model_iter_next (model, &row)); + } +} +@end + +// }}} +// {{{ Private + +@implementation EdgeStylesModel (Private) +- (cairo_surface_t*) createEdgeIconSurface { + return cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 48, 18); +} + +- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style { + cairo_surface_t *surface = [self createEdgeIconSurface]; + GdkPixbuf *pixbuf = [self pixbufOfEdgeInStyle:style usingSurface:surface]; + cairo_surface_destroy (surface); + return pixbuf; +} + +- (GdkPixbuf*) pixbufOfEdgeInStyle:(EdgeStyle*)style usingSurface:(cairo_surface_t*)surface { + Transformer *transformer = [Transformer defaultTransformer]; + [transformer setFlippedAboutXAxis:YES]; + + 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); + NSRect graphBounds = [transformer rectFromScreen:pixbufBounds]; + + NSPoint start = NSMakePoint (NSMinX (graphBounds) + 0.1f, NSMidY (graphBounds)); + NSPoint end = NSMakePoint (NSMaxX (graphBounds) - 0.1f, NSMidY (graphBounds)); + Node *src = [Node nodeWithPoint:start]; + Node *tgt = [Node nodeWithPoint:end]; + Edge *e = [Edge edgeWithSource:src andTarget:tgt]; + [e setStyle:style]; + + CairoRenderContext *context = [[CairoRenderContext alloc] initForSurface:surface]; + [context clearSurface]; + [e renderBasicEdgeInContext:context withTransformer:transformer selected:NO]; + [context release]; + + return pixbuf_get_from_surface (surface); +} + +- (void) addEdgeStyle:(EdgeStyle*)style usingSurface:(cairo_surface_t*)surface { + GtkTreeIter iter; + gtk_list_store_append (store, &iter); + + GdkPixbuf *pixbuf = [self pixbufOfEdgeInStyle:style usingSurface:surface]; + gtk_list_store_set (store, &iter, + EDGE_STYLES_NAME_COL, [[style name] UTF8String], + EDGE_STYLES_ICON_COL, pixbuf, + EDGE_STYLES_PTR_COL, (gpointer)[style retain], + -1); + g_object_unref (pixbuf); + [self observeEdgeStyle:style]; +} + +- (void) addEdgeStyle:(EdgeStyle*)style { + cairo_surface_t *surface = [self createEdgeIconSurface]; + [self addEdgeStyle:style usingSurface:surface]; + cairo_surface_destroy (surface); +} + +- (void) observeEdgeStyle:(EdgeStyle*)style { + [style addObserver:self + forKeyPath:@"name" + options:NSKeyValueObservingOptionNew + context:NULL]; + [style addObserver:self + forKeyPath:@"thickness" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"headStyle" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"tailStyle" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"decorationStyle" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"colorRGB.red" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"colorRGB.green" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"colorRGB.blue" + options:0 + context:NULL]; +} + +- (void) stopObservingEdgeStyle:(EdgeStyle*)style { + [style removeObserver:self forKeyPath:@"name"]; + [style removeObserver:self forKeyPath:@"thickness"]; + [style removeObserver:self forKeyPath:@"headStyle"]; + [style removeObserver:self forKeyPath:@"tailStyle"]; + [style removeObserver:self forKeyPath:@"decorationStyle"]; + [style removeObserver:self forKeyPath:@"colorRGB.red"]; + [style removeObserver:self forKeyPath:@"colorRGB.green"]; + [style removeObserver:self forKeyPath:@"colorRGB.blue"]; +} + +- (void) clearEdgeStylesModel { + GtkTreeModel *model = GTK_TREE_MODEL (store); + GtkTreeIter row; + if (gtk_tree_model_get_iter_first (model, &row)) { + do { + EdgeStyle *rowStyle; + gtk_tree_model_get (model, &row, EDGE_STYLES_PTR_COL, &rowStyle, -1); + [self stopObservingEdgeStyle:rowStyle]; + [rowStyle release]; + } while (gtk_tree_model_iter_next (model, &row)); + } + gtk_list_store_clear (store); +} + +- (void) reloadEdgeStyles { + [self clearEdgeStylesModel]; + cairo_surface_t *surface = [self createEdgeIconSurface]; + for (EdgeStyle *style in [styleManager edgeStyles]) { + [self addEdgeStyle:style usingSurface:surface]; + } + cairo_surface_destroy (surface); +} +@end + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/EdgeStylesPalette.h b/tikzit-1/src/gtk/EdgeStylesPalette.h new file mode 100644 index 0000000..c0c6c4b --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStylesPalette.h @@ -0,0 +1,43 @@ +/* + * Copyright 2012 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 EdgeStyleSelector; +@class EdgeStyleEditor; + +@interface EdgeStylesPalette: NSObject { + EdgeStyleSelector *selector; + EdgeStyleEditor *editor; + + GtkWidget *palette; + + GtkWidget *removeStyleButton; + GtkWidget *applyStyleButton; + GtkWidget *clearStyleButton; +} + +@property (retain) StyleManager *styleManager; +@property (readonly) GtkWidget *widget; + +- (id) initWithManager:(StyleManager*)m; + +@end + +// vim:ft=objc:ts=4:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/EdgeStylesPalette.m b/tikzit-1/src/gtk/EdgeStylesPalette.m new file mode 100644 index 0000000..33264cf --- /dev/null +++ b/tikzit-1/src/gtk/EdgeStylesPalette.m @@ -0,0 +1,198 @@ +/* + * Copyright 2012 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 "EdgeStylesPalette.h" + +#import "EdgeStylesModel.h" +#import "EdgeStyleSelector.h" +#import "EdgeStyleEditor.h" +#import "StyleManager.h" + +// {{{ Internal interfaces +// {{{ GTK+ Callbacks +static void add_style_button_cb (GtkButton *widget, EdgeStylesPalette *palette); +static void remove_style_button_cb (GtkButton *widget, EdgeStylesPalette *palette); +// }}} +// {{{ Notifications + +@interface EdgeStylesPalette (Notifications) +- (void) selectedStyleChanged:(NSNotification*)notification; +@end + +// }}} +// {{{ Private + +@interface EdgeStylesPalette (Private) +- (void) updateButtonState; +- (void) removeSelectedStyle; +- (void) addStyle; +@end + +// }}} +// }}} +// {{{ API + +@implementation EdgeStylesPalette + +@synthesize widget=palette; + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithManager:(StyleManager*)m { + self = [super init]; + + if (self) { + selector = [[EdgeStyleSelector alloc] initWithStyleManager:m]; + editor = [[EdgeStyleEditor alloc] init]; + + palette = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (palette), 6); + g_object_ref_sink (palette); + + GtkWidget *mainBox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (palette), mainBox, FALSE, FALSE, 0); + gtk_widget_show (mainBox); + + GtkWidget *selectorScroller = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (selectorScroller), + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + GtkWidget *selectorFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (selectorScroller), [selector widget]); + gtk_container_add (GTK_CONTAINER (selectorFrame), selectorScroller); + gtk_box_pack_start (GTK_BOX (mainBox), selectorFrame, TRUE, TRUE, 0); + gtk_widget_show (selectorScroller); + gtk_widget_show (selectorFrame); + gtk_widget_show ([selector widget]); + + gtk_box_pack_start (GTK_BOX (mainBox), [editor widget], TRUE, TRUE, 0); + gtk_widget_show ([editor widget]); + + GtkBox *buttonBox = GTK_BOX (gtk_hbox_new(FALSE, 0)); + gtk_box_pack_start (GTK_BOX (palette), GTK_WIDGET (buttonBox), 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 (buttonBox, 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 (buttonBox, removeStyleButton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (removeStyleButton), + "clicked", + G_CALLBACK (remove_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 model] styleManager]; +} + +- (void) setStyleManager:(StyleManager*)m { + [[selector model] setStyleManager:m]; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [editor release]; + [selector release]; + + g_object_unref (palette); + g_object_unref (removeStyleButton); + + [super dealloc]; +} + +@end + +// }}} +// {{{ Notifications + +@implementation EdgeStylesPalette (Notifications) +- (void) selectedStyleChanged:(NSNotification*)notification { + [editor setStyle:[selector selectedStyle]]; + [self updateButtonState]; +} +@end + +// }}} +// {{{ Private + +@implementation EdgeStylesPalette (Private) +- (void) updateButtonState { + gboolean hasStyleSelection = [selector selectedStyle] != nil; + gtk_widget_set_sensitive (removeStyleButton, hasStyleSelection); +} + +- (void) removeSelectedStyle { + EdgeStyle *style = [selector selectedStyle]; + if (style) + [[[selector model] styleManager] removeEdgeStyle:style]; +} + +- (void) addStyle { + EdgeStyle *newStyle = [EdgeStyle defaultEdgeStyleWithName:@"newstyle"]; + [[self styleManager] addEdgeStyle:newStyle]; + [selector setSelectedStyle:newStyle]; + [editor selectNameField]; +} + +@end + +// }}} +// {{{ GTK+ callbacks + +static void add_style_button_cb (GtkButton *widget, EdgeStylesPalette *palette) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [palette addStyle]; + [pool drain]; +} + +static void remove_style_button_cb (GtkButton *widget, EdgeStylesPalette *palette) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [palette removeSelectedStyle]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/FileChooserDialog.h b/tikzit-1/src/gtk/FileChooserDialog.h new file mode 100644 index 0000000..80b03f5 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/FileChooserDialog.m b/tikzit-1/src/gtk/FileChooserDialog.m new file mode 100644 index 0000000..9498e4c --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/GraphEditorPanel.h b/tikzit-1/src/gtk/GraphEditorPanel.h new file mode 100644 index 0000000..2b93259 --- /dev/null +++ b/tikzit-1/src/gtk/GraphEditorPanel.h @@ -0,0 +1,53 @@ +/* + * 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 "TZFoundation.h" +#import "Tool.h" +#import <gtk/gtk.h> + +@class GraphInputHandler; +@class GraphRenderer; +@class TikzDocument; +@class WidgetSurface; + +@protocol PreviewHandler <NSObject> +- (void) showPreview; +@end +@interface GraphEditorPanel : NSObject { + GraphRenderer *renderer; + WidgetSurface *surface; + GraphInputHandler *inputHandler; + id<PreviewHandler> previewHandler; + id<Tool> tool; +} +@property (retain) TikzDocument *document; +@property (readonly) GtkWidget *widget; +@property (retain) id<Tool> activeTool; +@property (assign) id<PreviewHandler> previewHandler; + +- (id) init; +- (id) initWithDocument:(TikzDocument*)document; +- (void) grabTool; +- (void) zoomInAboutPoint:(NSPoint)pos; +- (void) zoomOutAboutPoint:(NSPoint)pos; +- (void) zoomIn; +- (void) zoomOut; +- (void) zoomReset; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/GraphEditorPanel.m b/tikzit-1/src/gtk/GraphEditorPanel.m new file mode 100644 index 0000000..dac52a0 --- /dev/null +++ b/tikzit-1/src/gtk/GraphEditorPanel.m @@ -0,0 +1,240 @@ +/* + * 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 "GraphEditorPanel.h" + +#import "Application.h" +#import "GraphRenderer.h" +#import "HandTool.h" +#import "InputDelegate.h" +#import "TikzDocument.h" +#import "WidgetSurface.h" + +#import <gdk/gdkkeysyms.h> + +@class GraphRenderer; +@class WidgetSurface; + +static const InputMask zoomPanMask = ControlMask; + +/** + * Mostly just a multiplexer, but also handles zoom and pan + * when ctrl is held + */ +@interface GraphInputHandler : NSObject<InputDelegate> { + GraphEditorPanel *panel; + NSPoint dragOrigin; + NSPoint oldGraphOrigin; + BOOL zoomPanActive; +} +- (id) initForPanel:(GraphEditorPanel*)p; +@end + +@implementation GraphEditorPanel + +@synthesize previewHandler; + +- (id) init { + return [self initWithDocument:nil]; +} +- (id) initWithDocument:(TikzDocument*)document { + self = [super init]; + if (self) { + surface = [[WidgetSurface alloc] init]; + [surface setDefaultScale:50.0f]; + [surface setKeepCentered:YES]; + [surface setCanFocus:YES]; + renderer = [[GraphRenderer alloc] initWithSurface:surface document:document]; + + inputHandler = [[GraphInputHandler alloc] initForPanel:self]; + [surface setInputDelegate:inputHandler]; + } + return self; +} + +- (void) dealloc { + [renderer release]; + [surface release]; + [inputHandler release]; + + [super dealloc]; +} + +- (GraphRenderer*) renderer { + return renderer; +} +- (TikzDocument*) document { + return [renderer document]; +} +- (void) setDocument:(TikzDocument*)doc { + [renderer setDocument:doc]; +} +- (GtkWidget*) widget { + return [surface widget]; +} +- (id<Tool>) activeTool { + return tool; +} +- (void) setActiveTool:(id<Tool>)t { + if (t == tool) + return; + + [[[renderer document] pickSupport] deselectAllNodes]; + [[[renderer document] pickSupport] deselectAllEdges]; + + id oldTool = tool; + BOOL weHadTool = ([oldTool activeRenderer] == renderer); + if (weHadTool) { + [oldTool setActiveRenderer:nil]; + } + + tool = [t retain]; + [oldTool release]; + + if (weHadTool) { + [self grabTool]; + } +} + +- (BOOL) hasTool { + return [tool activeRenderer] == renderer; +} + +- (void) grabTool { + if ([tool activeRenderer] != renderer) { + [[tool activeRenderer] setPostRenderer:nil]; + [tool setActiveRenderer:renderer]; + } + [renderer setPostRenderer:tool]; +} + +- (void) zoomInAboutPoint:(NSPoint)pos { [surface zoomInAboutPoint:pos]; } +- (void) zoomOutAboutPoint:(NSPoint)pos { [surface zoomOutAboutPoint:pos]; } +- (void) zoomIn { [surface zoomIn]; } +- (void) zoomOut { [surface zoomOut]; } +- (void) zoomReset { [surface zoomReset]; } + +@end + +@implementation GraphInputHandler +- (id) initForPanel:(GraphEditorPanel*)p { + self = [super init]; + if (self) { + // NB: no retention! + panel = p; + } + return self; +} +- (id) init { + [self release]; + return nil; +} +- (void) dealloc { + [super dealloc]; +} + +// FIXME: share code with HandTool? +- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (mask == zoomPanMask && button == LeftButton) { + dragOrigin = pos; + oldGraphOrigin = [[[panel renderer] transformer] origin]; + zoomPanActive = YES; + } else { + zoomPanActive = NO; + [panel grabTool]; + id<Tool> tool = [panel activeTool]; + if ([tool respondsToSelector:@selector(mousePressAt:withButton:andMask:)]) { + [tool mousePressAt:pos withButton:button andMask:mask]; + } + } +} + +- (void) mouseDoubleClickAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + [panel grabTool]; + id<Tool> tool = [panel activeTool]; + if ([tool respondsToSelector:@selector(mouseDoubleClickAt:withButton:andMask:)]) { + [tool mouseDoubleClickAt:pos withButton:button andMask:mask]; + } +} + +- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (zoomPanActive && button == LeftButton) { + zoomPanActive = NO; + } else if ([panel hasTool]) { + id<Tool> tool = [panel activeTool]; + if ([tool respondsToSelector:@selector(mouseReleaseAt:withButton:andMask:)]) { + [tool mouseReleaseAt:pos withButton:button andMask:mask]; + } + } +} + +- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask { + if (zoomPanActive && (buttons & LeftButton)) { + NSPoint newGraphOrigin = oldGraphOrigin; + newGraphOrigin.x += pos.x - dragOrigin.x; + newGraphOrigin.y += pos.y - dragOrigin.y; + [[[panel renderer] transformer] setOrigin:newGraphOrigin]; + [[panel renderer] invalidateGraph]; + } else if ([panel hasTool]) { + id<Tool> tool = [panel activeTool]; + if ([tool respondsToSelector:@selector(mouseMoveTo:withButtons:andMask:)]) { + [tool mouseMoveTo:pos withButtons:buttons andMask:mask]; + } + } +} + +- (void) mouseScrolledAt:(NSPoint)pos inDirection:(ScrollDirection)dir withMask:(InputMask)mask { + if (mask == zoomPanMask) { + if (dir == ScrollUp) { + [panel zoomInAboutPoint:pos]; + } else if (dir == ScrollDown) { + [panel zoomOutAboutPoint:pos]; + } + } else { + id<Tool> tool = [panel activeTool]; + if ([panel hasTool] && [tool respondsToSelector:@selector(mouseScrolledAt:inDirection:withMask:)]) { + [tool mouseScrolledAt:pos inDirection:dir withMask:mask]; + } + } +} + +- (void) keyPressed:(unsigned int)keyVal withMask:(InputMask)mask { + if (keyVal == GDK_KEY_space && !mask) { + return; + } + if (![app activateToolForKey:keyVal withMask:mask]) { + id<Tool> tool = [panel activeTool]; + if ([panel hasTool] && [tool respondsToSelector:@selector(keyPressed:withMask:)]) { + [tool keyPressed:keyVal withMask:mask]; + } + } +} + +- (void) keyReleased:(unsigned int)keyVal withMask:(InputMask)mask { + if (keyVal == GDK_KEY_space && !mask) { + [[panel previewHandler] showPreview]; + } + if (![app activateToolForKey:keyVal withMask:mask]) { + id<Tool> tool = [panel activeTool]; + if ([panel hasTool] && [tool respondsToSelector:@selector(keyReleased:withMask:)]) { + [tool keyReleased:keyVal withMask:mask]; + } + } +} +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/GraphRenderer.h b/tikzit-1/src/gtk/GraphRenderer.h new file mode 100644 index 0000000..730d606 --- /dev/null +++ b/tikzit-1/src/gtk/GraphRenderer.h @@ -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 "TZFoundation.h" +#import <gtk/gtk.h> + +// classes +#import "Graph.h" +#import "Grid.h" +#import "PickSupport.h" +#import "TikzDocument.h" + +// protocols +#import "Surface.h" + +@interface GraphRenderer: NSObject <RenderDelegate> { + TikzDocument *doc; + NSObject<Surface> *surface; + Grid *grid; + NSMutableSet *highlightedNodes; + id<RenderDelegate> postRenderer; +} + +@property (retain) id<RenderDelegate> postRenderer; + +- (id) initWithSurface:(NSObject <Surface> *)surface; +- (id) initWithSurface:(NSObject <Surface> *)surface document:(TikzDocument*)document; +- (void) renderWithContext:(id<RenderContext>)context; +- (void) invalidateRect:(NSRect)rect; +- (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; + +- (BOOL) isNodeHighlighted:(Node*)node; +- (void) setNode:(Node*)node highlighted:(BOOL)h; +- (void) clearHighlightedNodes; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/GraphRenderer.m b/tikzit-1/src/gtk/GraphRenderer.m new file mode 100644 index 0000000..b413d3e --- /dev/null +++ b/tikzit-1/src/gtk/GraphRenderer.m @@ -0,0 +1,476 @@ +/* + * 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" +#import "Shape.h" + +void graph_renderer_expose_event(GtkWidget *widget, GdkEventExpose *event); + +@interface GraphRenderer (Private) +- (enum NodeState) nodeState:(Node*)node; +- (void) renderBoundingBoxWithContext:(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; +- (void) shapeDictionaryReplaced:(NSNotification*)notification; +@end + +@implementation GraphRenderer + +- (id) initWithSurface:(NSObject <Surface> *)s { + self = [super init]; + + if (self) { + surface = [s retain]; + grid = [[Grid alloc] initWithSpacing:1.0f subdivisions:4 transformer:[s transformer]]; + highlightedNodes = [[NSMutableSet alloc] initWithCapacity:10]; + [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]; + [highlightedNodes release]; + [surface release]; + + [super dealloc]; +} + +- (id<RenderDelegate>) postRenderer { + return postRenderer; +} +- (void) setPostRenderer:(id<RenderDelegate>)r { + if (r == postRenderer) + return; + + [r retain]; + [postRenderer release]; + postRenderer = r; + + [self invalidateGraph]; +} + +- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)s { + [self renderWithContext:context]; + if ([s hasFocus]) { + [s renderFocus]; + } +} + +- (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]; + [postRenderer renderWithContext:context onSurface:surface]; +} + +- (void) invalidateGraph { + [surface invalidate]; +} + +- (void) invalidateRect:(NSRect)rect { + [surface invalidateRect:rect]; +} + +- (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 renderBoundsWithLabelForSurface: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 renderBoundsForSurface: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]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(shapeDictionaryReplaced:) + name:@"ShapeDictionaryReplaced" + object:[Shape class]]; + } + [surface invalidate]; +} + +- (BOOL) isNodeHighlighted:(Node*)node { + return [highlightedNodes containsObject:node]; +} +- (void) setNode:(Node*)node highlighted:(BOOL)h { + if (h) { + if (![highlightedNodes containsObject:node]) { + [highlightedNodes addObject:node]; + [self invalidateNode:node]; + } + } else { + if ([highlightedNodes containsObject:node]) { + [highlightedNodes removeObject:node]; + [self invalidateNode:node]; + } + } +} +- (void) clearHighlightedNodes { + [self invalidateNodes:highlightedNodes]; + [highlightedNodes removeAllObjects]; +} + +@end + +@implementation GraphRenderer (Private) +- (enum NodeState) nodeState:(Node*)node { + if ([doc isNodeSelected:node]) { + return NodeSelected; + } else if ([self isNodeHighlighted: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)]; + + [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) invalidateBentIncidentEdgesForNode:(Node*)nd { + for (Edge *e in [[self graph] inEdgesForNode:nd]) { + if (![e isStraight]) { + [self invalidateEdge:e]; + } + } + for (Edge *e in [[self graph] outEdgesForNode:nd]) { + if (![e isStraight]) { + [self invalidateEdge:e]; + } + } +} + +- (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 if ([[change oldNode] style] != [[change nwNode] style]) { + // change in style means that edges may touch at a different point, + // but this only matters for bent edges + [self invalidateBentIncidentEdgesForNode:[change nodeRef]]; + // invalide both old and new (old node may be larger) + [self invalidateNode:[change oldNode]]; + [self invalidateNode:[change nwNode]]; + } 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]; + NodeStyle *oldStyle = [[[change oldNodeTable] objectForKey:node] style]; + NodeStyle *newStyle = [[[change nwNodeTable] objectForKey:node] style]; + if (!NSEqualPoints (oldPos, newPos)) { + [surface invalidate]; + break; + } else if (oldStyle != newStyle) { + [self invalidateBentIncidentEdgesForNode:node]; + [self invalidateNode:[[change oldNodeTable] objectForKey:node]]; + [self invalidateNode:[[change nwNodeTable] objectForKey:node]]; + } else { + [self invalidateNode:[[change oldNodeTable] objectForKey:node]]; + [self invalidateNode:[[change nwNodeTable] objectForKey:node]]; + } + } + } + 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]; + } +} + +- (void) shapeDictionaryReplaced:(NSNotification*)notification { + [surface invalidate]; +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/HandTool.h b/tikzit-1/src/gtk/HandTool.h new file mode 100644 index 0000000..c96de36 --- /dev/null +++ b/tikzit-1/src/gtk/HandTool.h @@ -0,0 +1,33 @@ +/* + * 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 "TZFoundation.h" +#import "Tool.h" + +@interface HandTool : NSObject <Tool> { + GraphRenderer *renderer; + NSPoint dragOrigin; + NSPoint oldGraphOrigin; +} + + ++ (id) tool; +- (id) init; +@end + + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/HandTool.m b/tikzit-1/src/gtk/HandTool.m new file mode 100644 index 0000000..c3a0fb4 --- /dev/null +++ b/tikzit-1/src/gtk/HandTool.m @@ -0,0 +1,75 @@ +/* + * Copyright 2011-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 "HandTool.h" + +#import "GraphRenderer.h" +#import "TikzDocument.h" +#import "tzstockitems.h" + +@implementation HandTool +- (NSString*) name { return @"Drag"; } +- (const gchar*) stockId { return TIKZIT_STOCK_DRAG; } +- (NSString*) helpText { return @"Move the diagram to view different parts"; } +- (NSString*) shortcut { return @"m"; } +@synthesize activeRenderer=renderer; + ++ (id) tool { + return [[[self alloc] init] autorelease]; +} + +- (id) init { + return [super init]; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [renderer release]; + + [super dealloc]; +} + +- (GtkWidget*) configurationWidget { return NULL; } + +- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + + dragOrigin = pos; + oldGraphOrigin = [[renderer transformer] origin]; +} + +- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask { + if (!(buttons & LeftButton)) + return; + + NSPoint newGraphOrigin = oldGraphOrigin; + newGraphOrigin.x += pos.x - dragOrigin.x; + newGraphOrigin.y += pos.y - dragOrigin.y; + [[renderer transformer] setOrigin:newGraphOrigin]; + [renderer invalidateGraph]; +} + +- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask {} + +- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface {} +- (void) loadConfiguration:(Configuration*)config {} +- (void) saveConfiguration:(Configuration*)config {} +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/InputDelegate.h b/tikzit-1/src/gtk/InputDelegate.h new file mode 100644 index 0000000..9f9b426 --- /dev/null +++ b/tikzit-1/src/gtk/InputDelegate.h @@ -0,0 +1,77 @@ +/* + * 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; + +@protocol InputDelegate <NSObject> +@optional +/** + * 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-1/src/gtk/Menu.h b/tikzit-1/src/gtk/Menu.h new file mode 100644 index 0000000..e0f78d4 --- /dev/null +++ b/tikzit-1/src/gtk/Menu.h @@ -0,0 +1,86 @@ +/* + * 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> + +@class Window; +@class PickSupport; + +/** + * Manages the menu + */ +@interface Menu: NSObject { + GtkWidget *menubar; + GtkActionGroup *appActions; + GtkActionGroup *windowActions; + GtkAction *undoAction; // no ref + GtkAction *redoAction; // no ref + GtkAction *pasteAction; // no ref + GtkAction **nodeSelBasedActions; + guint nodeSelBasedActionCount; + GtkAction **edgeSelBasedActions; + guint edgeSelBasedActionCount; + GtkAction **selBasedActions; + guint selBasedActionCount; +} + +/** + * The menubar widget, to be inserted into the window + */ +@property (readonly) GtkWidget *menubar; + +/** + * Constructs the menu for @p window + * + * @param window the window that will be acted upon + */ +- (id) initForWindow:(Window*)window; + +/** + * 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-1/src/gtk/Menu.m b/tikzit-1/src/gtk/Menu.m new file mode 100644 index 0000000..04c9c31 --- /dev/null +++ b/tikzit-1/src/gtk/Menu.m @@ -0,0 +1,737 @@ +/* + * 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 "Application.h" +#import "Window.h" +#import "Configuration.h" +#import "PickSupport.h" +#import "Shape.h" +#import "Tool.h" +#import "TikzDocument.h" + +#import <glib.h> +#ifdef _ +#undef _ +#endif +#import <glib/gi18n.h> +#import <gtk/gtk.h> + +#import "gtkhelpers.h" + +#import "logo.h" + +// {{{ Application actions +static void new_cb (GtkAction *action, Application *appl) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [appl newWindow]; + [pool drain]; +} + +static void refresh_shapes_cb (GtkAction *action, Application *appl) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [Shape refreshShapeDictionary]; + [pool drain]; +} + +static void show_preferences_cb (GtkAction *action, Application *appl) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [appl presentSettingsDialog]; + [pool drain]; +} + +#ifdef HAVE_POPPLER +static void show_preamble_cb (GtkAction *action, Application *appl) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [appl presentPreamblesEditor]; + [pool drain]; +} +#endif + +static void show_context_window_cb (GtkAction *action, Application *appl) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [appl presentContextWindow]; + [pool drain]; +} + +static void quit_cb (GtkAction *action, Application *appl) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [appl quit]; + [pool drain]; +} + +static void help_cb (GtkAction *action, Application *appl) { + 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, Application *appl) { + 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 (NULL, + "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 GtkActionEntry app_action_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 + */ + { "New", GTK_STOCK_NEW, NULL, "<control>N", + N_("Create a new graph"), G_CALLBACK (new_cb) }, + + { "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) }, + + { "Tool", NULL, N_("_Tool") }, + + { "ShowPreferences", GTK_STOCK_PREFERENCES, N_("Configure TikZiT..."), NULL, + N_("Edit the TikZiT configuration"), G_CALLBACK (show_preferences_cb) }, + +#ifdef HAVE_POPPLER + { "ShowPreamble", NULL, N_("_Edit Preambles..."), NULL, + N_("Edit the preambles used to generate the preview"), G_CALLBACK (show_preamble_cb) }, +#endif + + { "ShowContextWindow", NULL, N_("_Context Window"), NULL, + N_("Show the contextual tools window"), G_CALLBACK (show_context_window_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_app_action_entries = G_N_ELEMENTS (app_action_entries); +// }}} +// {{{ Window actions + +static void open_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window openFile]; + [pool drain]; +} + +static void close_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window close]; + [pool drain]; +} + +static void save_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window saveActiveDocument]; + [pool drain]; +} + +static void save_as_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window saveActiveDocumentAs]; + [pool drain]; +} + +static void save_as_shape_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window saveActiveDocumentAsShape]; + [pool drain]; +} + +static void undo_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + TikzDocument *document = [window document]; + 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, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + TikzDocument *document = [window document]; + 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, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window selectionCutToClipboard]; + [pool drain]; +} + +static void copy_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window selectionCopyToClipboard]; + [pool drain]; +} + +static void paste_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window pasteFromClipboard]; + [pool drain]; +} + +static void delete_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] removeSelected]; + [pool drain]; +} + +static void select_all_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TikzDocument *document = [window document]; + [[document pickSupport] selectAllNodes:[NSSet setWithArray:[[document graph] nodes]]]; + [pool drain]; +} + +static void deselect_all_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + TikzDocument *document = [window document]; + [[document pickSupport] deselectAllNodes]; + [[document pickSupport] deselectAllEdges]; + [pool drain]; +} + +static void flip_horiz_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] flipSelectedNodesHorizontally]; + [pool drain]; +} + +static void flip_vert_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] flipSelectedNodesVertically]; + [pool drain]; +} + +static void reverse_edges_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] reverseSelectedEdges]; + [pool drain]; +} + +static void bring_forward_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] bringSelectionForward]; + [pool drain]; +} + +static void send_backward_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] sendSelectionBackward]; + [pool drain]; +} + +static void bring_to_front_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] bringSelectionToFront]; + [pool drain]; +} + +static void send_to_back_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[window document] sendSelectionToBack]; + [pool drain]; +} + +#ifdef HAVE_POPPLER +static void show_preview_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window presentPreview]; + [pool drain]; +} +#endif + +static void zoom_in_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window zoomIn]; + [pool drain]; +} + +static void zoom_out_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window zoomOut]; + [pool drain]; +} + +static void zoom_reset_cb (GtkAction *action, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window zoomReset]; + [pool drain]; +} + +static void recent_chooser_item_activated_cb (GtkRecentChooser *chooser, Window *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; + } + + [window openFileAtPath:[NSString stringWithGlibFilename:path]]; + + g_free (uri); + g_free (path); + + [pool drain]; +} + + +static GtkActionEntry window_action_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") }, + { "HelpMenu", NULL, N_("_Help") }, + + { "Arrange", NULL, N_("_Arrange") }, + { "Zoom", NULL, N_("_Zoom") }, + + { "Open", GTK_STOCK_OPEN, N_("_Open\342\200\246") ,"<control>O", + N_("Open a graph"), G_CALLBACK (open_cb) }, + + { "Close", GTK_STOCK_CLOSE, NULL, "<control>W", + N_("Close the current graph"), G_CALLBACK (close_cb) }, + + { "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) }, + + { "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) }, + + { "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) }, + + { "ReverseEdges", NULL, N_("Rever_se edges"), NULL, + N_("Reverse the selected edges"), G_CALLBACK (reverse_edges_cb) }, + + { "SendToBack", NULL, N_("Send to _back"), NULL, + N_("Send the selected nodes and edges to the back of the graph"), G_CALLBACK (send_to_back_cb) }, + + { "SendBackward", NULL, N_("Send b_ackward"), NULL, + N_("Send the selected nodes and edges backward"), G_CALLBACK (send_backward_cb) }, + + { "BringForward", NULL, N_("Bring f_orward"), NULL, + N_("Bring the selected nodes and edges forward"), G_CALLBACK (bring_forward_cb) }, + + { "BringToFront", NULL, N_("Bring to _front"), NULL, + N_("Bring the selected nodes and edges to the front of the graph"), G_CALLBACK (bring_to_front_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_window_action_entries = G_N_ELEMENTS (window_action_entries); + +// }}} +// {{{ 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'>" +" </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'/>" +" <menuitem action='ReverseEdges'/>" +" <separator/>" +" <menu action='Arrange'>" +" <menuitem action='SendToBack'/>" +" <menuitem action='SendBackward'/>" +" <menuitem action='BringForward'/>" +" <menuitem action='BringToFront'/>" +" </menu>" +" <separator/>" +" <menuitem action='ShowPreferences'/>" +" </menu>" +" <menu action='ViewMenu'>" +" <menuitem action='ShowContextWindow'/>" +#ifdef HAVE_POPPLER +" <menuitem action='ShowPreamble'/>" +" <menuitem action='ShowPreview'/>" +#endif +" <menu action='Zoom'>" +" <menuitem action='ZoomIn'/>" +" <menuitem action='ZoomOut'/>" +" <menuitem action='ZoomReset'/>" +" </menu>" +" </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>"; + + + +// }}} +// {{{ Helper methods + +static void configure_recent_chooser (GtkRecentChooser *chooser) +{ + gtk_recent_chooser_set_local_only (chooser, TRUE); + gtk_recent_chooser_set_show_icons (chooser, FALSE); + gtk_recent_chooser_set_sort_type (chooser, GTK_RECENT_SORT_MRU); + + GtkRecentFilter *filter = gtk_recent_filter_new (); + gtk_recent_filter_add_application (filter, g_get_application_name()); + gtk_recent_chooser_set_filter (chooser, filter); +} + +static void tool_cb (GtkAction *action, id<Tool> tool) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [app setActiveTool:tool]; + [pool drain]; +} + + + +// }}} +// {{{ API + +@implementation Menu + +- (id) init { + [self release]; + return nil; +} + +- (id) initForWindow:(Window*)window { + self = [super init]; + if (!self) { + return nil; + } + + GError *error = NULL; + + appActions = gtk_action_group_new ("TZApp"); + //gtk_action_group_set_translation_domain (actions, GETTEXT_PACKAGE); + gtk_action_group_add_actions (appActions, + app_action_entries, + n_app_action_entries, + app); + for (id<Tool> tool in [app tools]) { + NSString *tooltip = [NSString stringWithFormat: + @"%@: %@ (%@)", [tool name], [tool helpText], [tool shortcut]]; + GtkAction *action = gtk_action_new ( + [[tool name] UTF8String], + [[tool name] UTF8String], + [tooltip UTF8String], + [tool stockId]); + gtk_action_group_add_action_with_accel ( + appActions, + action, + NULL); + g_signal_connect ( + G_OBJECT (action), + "activate", + G_CALLBACK (tool_cb), + tool); + g_object_unref (action); + } + + windowActions = gtk_action_group_new ("TZWindow"); + //gtk_action_group_set_translation_domain (windowActions, GETTEXT_PACKAGE); + + gtk_action_group_add_actions (windowActions, + window_action_entries, + n_window_action_entries, + window); + + GtkAction *action = gtk_recent_action_new ("OpenRecent", N_("Open _Recent"), NULL, NULL); + g_signal_connect (G_OBJECT (action), + "item-activated", + G_CALLBACK (recent_chooser_item_activated_cb), + window); + configure_recent_chooser (GTK_RECENT_CHOOSER (action)); + gtk_action_group_add_action_with_accel (windowActions, action, NULL); + g_object_unref (action); + + /* Save refs to actions that will need to be updated */ + undoAction = gtk_action_group_get_action (windowActions, "Undo"); + redoAction = gtk_action_group_get_action (windowActions, "Redo"); + pasteAction = gtk_action_group_get_action (windowActions, "Paste"); + + nodeSelBasedActionCount = 4; + nodeSelBasedActions = g_new (GtkAction*, nodeSelBasedActionCount); + nodeSelBasedActions[0] = gtk_action_group_get_action (windowActions, "Cut"); + nodeSelBasedActions[1] = gtk_action_group_get_action (windowActions, "Copy"); + nodeSelBasedActions[2] = gtk_action_group_get_action (windowActions, "FlipHoriz"); + nodeSelBasedActions[3] = gtk_action_group_get_action (windowActions, "FlipVert"); + edgeSelBasedActionCount = 1; + edgeSelBasedActions = g_new (GtkAction*, edgeSelBasedActionCount); + edgeSelBasedActions[0] = gtk_action_group_get_action (windowActions, "ReverseEdges"); + selBasedActionCount = 2; + selBasedActions = g_new (GtkAction*, selBasedActionCount); + selBasedActions[0] = gtk_action_group_get_action (windowActions, "Delete"); + selBasedActions[1] = gtk_action_group_get_action (windowActions, "DeselectAll"); + + + GtkUIManager *ui = gtk_ui_manager_new (); + gtk_ui_manager_insert_action_group (ui, windowActions, 0); + gtk_ui_manager_insert_action_group (ui, appActions, 1); + 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); + g_object_unref (ui); + [self release]; + return nil; + } + guint tool_merge_id = gtk_ui_manager_new_merge_id (ui); + for (id<Tool> tool in [app tools]) { + gtk_ui_manager_add_ui (ui, + tool_merge_id, + "/ui/MenuBar/EditMenu/Tool", + [[tool name] UTF8String], + [[tool name] UTF8String], + GTK_UI_MANAGER_AUTO, + FALSE); + } + menubar = gtk_ui_manager_get_widget (ui, "/MenuBar"); + g_object_ref_sink (menubar); + g_object_unref (ui); + + return self; +} + +- (void) dealloc { + g_free (nodeSelBasedActions); + g_free (edgeSelBasedActions); + g_free (selBasedActions); + g_object_unref (menubar); + g_object_unref (appActions); + g_object_unref (windowActions); + + [super dealloc]; +} + +@synthesize menubar; + +- (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 < edgeSelBasedActionCount; ++i) { + if (edgeSelBasedActions[i]) { + gtk_action_set_sensitive (edgeSelBasedActions[i], hasSelectedEdges); + } + } + for (int i = 0; i < selBasedActionCount; ++i) { + if (selBasedActions[i]) { + gtk_action_set_sensitive (selBasedActions[i], hasSelectedNodes || hasSelectedEdges); + } + } +} + +@end + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/NSError+Glib.h b/tikzit-1/src/gtk/NSError+Glib.h new file mode 100644 index 0000000..137977e --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/NSError+Glib.m b/tikzit-1/src/gtk/NSError+Glib.m new file mode 100644 index 0000000..f466d9e --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/NSFileManager+Glib.h b/tikzit-1/src/gtk/NSFileManager+Glib.h new file mode 100644 index 0000000..cb49fcb --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/NSFileManager+Glib.m b/tikzit-1/src/gtk/NSFileManager+Glib.m new file mode 100644 index 0000000..b3e9de6 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/NSString+Glib.h b/tikzit-1/src/gtk/NSString+Glib.h new file mode 100644 index 0000000..ac59833 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/NSString+Glib.m b/tikzit-1/src/gtk/NSString+Glib.m new file mode 100644 index 0000000..b6dc765 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/Node+Render.h b/tikzit-1/src/gtk/Node+Render.h new file mode 100644 index 0000000..60d2573 --- /dev/null +++ b/tikzit-1/src/gtk/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) renderBoundsForSurface:(id<Surface>)surface; +- (NSRect) renderBoundsWithLabelForSurface:(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-1/src/gtk/Node+Render.m b/tikzit-1/src/gtk/Node+Render.m new file mode 100644 index 0000000..907d818 --- /dev/null +++ b/tikzit-1/src/gtk/Node+Render.m @@ -0,0 +1,188 @@ +/* + * 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 MAX_LABEL_LENGTH 10 +#define LABEL_PADDING_X 2 +#define LABEL_PADDING_Y 2 + +@implementation Node (Render) + +- (Shape*) shapeToRender { + if (style) { + return [Shape shapeForName:[style shapeName]]; + } else { + return [Shape shapeForName:SHAPE_CIRCLE]; + } +} + +- (Transformer*) shapeTransformerForSurface:(id<Surface>)surface { + return [self shapeTransformerFromTransformer:[surface transformer]]; +} + +- (NSRect) renderBoundsUsingShapeTransform:(Transformer*)shapeTrans { + float strokeThickness = style ? [style strokeThickness] : [NodeStyle defaultStrokeThickness]; + NSRect screenBounds = [shapeTrans rectToScreen:[[self shapeToRender] boundingRect]]; + screenBounds = NSInsetRect(screenBounds, -strokeThickness, -strokeThickness); + return screenBounds; +} + +- (NSRect) renderBoundsForSurface:(id<Surface>)surface { + return [self renderBoundsUsingShapeTransform:[self shapeTransformerForSurface:surface]]; +} + +- (NSRect) renderBoundsWithLabelForSurface:(id<Surface>)surface { + NSRect nodeBounds = [self renderBoundsForSurface: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] : [NodeStyle defaultStrokeThickness]; + + [context saveState]; + + [[self shapeToRender] 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 shapeToRender] 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 renderBoundsUsingShapeTransform:shapeTrans]; + if (!NSPointInRect(p, screenBounds)) { + return NO; + } + + float strokeThickness = style ? [style strokeThickness] : [NodeStyle defaultStrokeThickness]; + id<RenderContext> ctx = [surface createRenderContext]; + [ctx setLineWidth:strokeThickness]; + [[self shapeToRender] 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-1/src/gtk/NodeStyle+Gtk.h b/tikzit-1/src/gtk/NodeStyle+Gtk.h new file mode 100644 index 0000000..4fa5edd --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/NodeStyle+Gtk.m b/tikzit-1/src/gtk/NodeStyle+Gtk.m new file mode 100644 index 0000000..1954def --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/NodeStyle+Render.h b/tikzit-1/src/gtk/NodeStyle+Render.h new file mode 100644 index 0000000..00edd27 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/NodeStyle+Storage.h b/tikzit-1/src/gtk/NodeStyle+Storage.h new file mode 100644 index 0000000..7649414 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/NodeStyle+Storage.m b/tikzit-1/src/gtk/NodeStyle+Storage.m new file mode 100644 index 0000000..088b062 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/NodeStyleEditor.h b/tikzit-1/src/gtk/NodeStyleEditor.h new file mode 100644 index 0000000..b45c992 --- /dev/null +++ b/tikzit-1/src/gtk/NodeStyleEditor.h @@ -0,0 +1,45 @@ +/* + * 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; + +- (void) selectNameField; + +@end + +// vim:ft=objc:ts=4:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/NodeStyleEditor.m b/tikzit-1/src/gtk/NodeStyleEditor.m new file mode 100644 index 0000000..fcf4147 --- /dev/null +++ b/tikzit-1/src/gtk/NodeStyleEditor.m @@ -0,0 +1,477 @@ +/* + * 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_misc_set_alignment (GTK_MISC (l), 0, 0.5); + 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_table_set_col_spacings (table, 6); + gtk_table_set_row_spacings (table, 6); + 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); +} + +- (void) selectNameField { + gtk_widget_grab_focus (GTK_WIDGET (nameEdit)); + gtk_editable_select_region (GTK_EDITABLE (nameEdit), 0, -1); +} + +@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:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/NodeStyleSelector.h b/tikzit-1/src/gtk/NodeStyleSelector.h new file mode 100644 index 0000000..a699dc8 --- /dev/null +++ b/tikzit-1/src/gtk/NodeStyleSelector.h @@ -0,0 +1,61 @@ +/* + * 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; +@class NodeStylesModel; +@class StyleManager; + +@interface NodeStyleSelector: NSObject { + NodeStylesModel *model; + GtkIconView *view; +} + +/*! + @property widget + @brief The GTK widget + */ +@property (readonly) GtkWidget *widget; + +/*! + @property model + @brief The model to use. + */ +@property (retain) NodeStylesModel *model; + +/*! + @property selectedStyle + @brief The selected style. + + When this changes, a SelectedStyleChanged notification will be posted + */ +@property (assign) NodeStyle *selectedStyle; + +/*! + * Initialise with a new model for the given style manager + */ +- (id) initWithStyleManager:(StyleManager*)manager; +/*! + * Initialise with the given model + */ +- (id) initWithModel:(NodeStylesModel*)model; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/NodeStyleSelector.m b/tikzit-1/src/gtk/NodeStyleSelector.m new file mode 100644 index 0000000..14cdc75 --- /dev/null +++ b/tikzit-1/src/gtk/NodeStyleSelector.m @@ -0,0 +1,135 @@ +/* + * Copyright 2011-2012 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 "NodeStylesModel.h" + +// {{{ Internal interfaces +static void selection_changed_cb (GtkIconView *widget, NodeStyleSelector *mgr); +// }}} +// {{{ API + +@implementation NodeStyleSelector + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)m { + return [self initWithModel:[NodeStylesModel modelWithStyleManager:m]]; +} +- (id) initWithModel:(NodeStylesModel*)m { + self = [super init]; + + if (self) { + model = [m retain]; + + view = GTK_ICON_VIEW (gtk_icon_view_new ()); + g_object_ref_sink (view); + + gtk_icon_view_set_model (view, [model model]); + gtk_icon_view_set_pixbuf_column (view, NODE_STYLES_ICON_COL); + gtk_icon_view_set_tooltip_column (view, NODE_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); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + g_object_unref (view); + [model release]; + + [super dealloc]; +} + +- (NodeStylesModel*) model { + return model; +} + +- (void) setModel:(NodeStylesModel*)m { + if (m == model) + return; + + NodeStylesModel *oldModel = model; + model = [m retain]; + gtk_icon_view_set_model (view, [model model]); + [oldModel release]; +} + +- (GtkWidget*) widget { + return GTK_WIDGET (view); +} + +- (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; + NodeStyle *style = [model styleFromPath:path]; + + 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; + } + + GtkTreePath *path = [model pathFromStyle:style]; + if (path) { + gtk_icon_view_unselect_all (view); + gtk_icon_view_select_path (view, path); + gtk_tree_path_free (path); + } +} + +@end + +// }}} +// {{{ GTK+ callbacks + +static void selection_changed_cb (GtkIconView *view, NodeStyleSelector *mgr) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + [[NSNotificationCenter defaultCenter] + postNotificationName:@"SelectedStyleChanged" + object:mgr]; + + [pool drain]; +} +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/NodeStylesModel.h b/tikzit-1/src/gtk/NodeStylesModel.h new file mode 100644 index 0000000..a048560 --- /dev/null +++ b/tikzit-1/src/gtk/NodeStylesModel.h @@ -0,0 +1,62 @@ +/* + * Copyright 2011-2012 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; +@class StyleManager; + +enum { + NODE_STYLES_NAME_COL = 0, + NODE_STYLES_ICON_COL, + NODE_STYLES_PTR_COL, + NODE_STYLES_N_COLS +}; + +@interface NodeStylesModel: NSObject { + GtkListStore *store; + StyleManager *styleManager; +} + +/*! + @property model + @brief The GTK+ tree model + */ +@property (readonly) GtkTreeModel *model; + +/*! + @property manager + @brief The StyleManager to use. + */ +@property (retain) StyleManager *styleManager; + +/*! + * Initialise with the given style manager + */ +- (id) initWithStyleManager:(StyleManager*)m; + ++ (id) modelWithStyleManager:(StyleManager*)m; + +- (NodeStyle*) styleFromPath:(GtkTreePath*)path; +- (GtkTreePath*) pathFromStyle:(NodeStyle*)style; +- (NodeStyle*) styleFromIter:(GtkTreeIter*)iter; +- (GtkTreeIter*) iterFromStyle:(NodeStyle*)style; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/NodeStylesModel.m b/tikzit-1/src/gtk/NodeStylesModel.m new file mode 100644 index 0000000..3cc5771 --- /dev/null +++ b/tikzit-1/src/gtk/NodeStylesModel.m @@ -0,0 +1,381 @@ +/* + * Copyright 2011-2012 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 "NodeStylesModel.h" + +#import "CairoRenderContext.h" +#import "NodeStyle.h" +#import "Shape.h" +#import "Shape+Render.h" +#import "ShapeNames.h" +#import "StyleManager.h" + +#import "gtkhelpers.h" + +#import <gdk-pixbuf/gdk-pixbuf.h> + +// {{{ Internal interfaces + +@interface NodeStylesModel (Notifications) +- (void) nodeStylesReplaced:(NSNotification*)notification; +- (void) nodeStyleAdded:(NSNotification*)notification; +- (void) nodeStyleRemoved:(NSNotification*)notification; +- (void) shapeDictionaryReplaced:(NSNotification*)n; +- (void) observeValueForKeyPath:(NSString*)keyPath + ofObject:(id)object + change:(NSDictionary*)change + context:(void*)context; +@end + +@interface NodeStylesModel (Private) +- (cairo_surface_t*) createNodeIconSurface; +- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style; +- (GdkPixbuf*) pixbufOfNodeInStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface; +- (void) addNodeStyle:(NodeStyle*)style; +- (void) addNodeStyle:(NodeStyle*)style usingSurface:(cairo_surface_t*)surface; +- (void) observeNodeStyle:(NodeStyle*)style; +- (void) stopObservingNodeStyle:(NodeStyle*)style; +- (void) clearNodeStylesModel; +- (void) reloadNodeStyles; +@end + +// }}} +// {{{ API + +@implementation NodeStylesModel + ++ (id) modelWithStyleManager:(StyleManager*)m { + return [[[self alloc] initWithStyleManager:m] autorelease]; +} + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)m { + self = [super init]; + + if (self) { + store = gtk_list_store_new (NODE_STYLES_N_COLS, + G_TYPE_STRING, + GDK_TYPE_PIXBUF, + G_TYPE_POINTER); + g_object_ref_sink (store); + + [self setStyleManager:m]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(shapeDictionaryReplaced:) + name:@"ShapeDictionaryReplaced" + object:[Shape class]]; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [self clearNodeStylesModel]; + g_object_unref (store); + [styleManager release]; + + [super dealloc]; +} + +- (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(nodeStylesReplaced:) + name:@"NodeStylesReplaced" + object:m]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(nodeStyleAdded:) + name:@"NodeStyleAdded" + object:m]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(nodeStyleRemoved:) + name:@"NodeStyleRemoved" + object:m]; + + [styleManager release]; + styleManager = m; + + [self reloadNodeStyles]; +} + +- (GtkTreeModel*) model { + return GTK_TREE_MODEL (store); +} + +- (NodeStyle*) styleFromPath:(GtkTreePath*)path { + 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, NODE_STYLES_PTR_COL, &style, -1); + return style; +} + +- (GtkTreePath*) pathFromStyle:(NodeStyle*)style { + 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, NODE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + return gtk_tree_model_get_path (m, &row); + } + } while (gtk_tree_model_iter_next (m, &row)); + } + return NULL; +} + +- (NodeStyle*) styleFromIter:(GtkTreeIter*)iter { + NodeStyle *style = nil; + gtk_tree_model_get (GTK_TREE_MODEL (store), iter, NODE_STYLES_PTR_COL, &style, -1); + return style; +} + +- (GtkTreeIter*) iterFromStyle:(NodeStyle*)style { + 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, NODE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + return gtk_tree_iter_copy (&row); + } + } while (gtk_tree_model_iter_next (m, &row)); + } + return NULL; +} +@end + +// }}} +// {{{ Notifications + +@implementation NodeStylesModel (Notifications) + +- (void) nodeStylesReplaced:(NSNotification*)notification { + [self reloadNodeStyles]; +} + +- (void) nodeStyleAdded:(NSNotification*)notification { + [self addNodeStyle:[[notification userInfo] objectForKey:@"style"]]; +} + +- (void) nodeStyleRemoved:(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, NODE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + gtk_list_store_remove (store, &row); + [self stopObservingNodeStyle:rowStyle]; + [rowStyle release]; + return; + } + } while (gtk_tree_model_iter_next (model, &row)); + } +} + +- (void) observeValueForKeyPath:(NSString*)keyPath + ofObject:(id)object + change:(NSDictionary*)change + context:(void*)context +{ + if ([object class] == [NodeStyle class]) { + NodeStyle *style = 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, NODE_STYLES_PTR_COL, &rowStyle, -1); + if (style == rowStyle) { + if ([@"name" isEqual:keyPath]) { + gtk_list_store_set (store, &row, NODE_STYLES_NAME_COL, [[style name] UTF8String], -1); + } else { + GdkPixbuf *pixbuf = [self pixbufOfNodeInStyle:style]; + gtk_list_store_set (store, &row, NODE_STYLES_ICON_COL, pixbuf, -1); + g_object_unref (pixbuf); + } + } + } while (gtk_tree_model_iter_next (model, &row)); + } + } +} + +- (void) shapeDictionaryReplaced:(NSNotification*)n { + [self reloadNodeStyles]; +} +@end + +// }}} +// {{{ Private + +@implementation NodeStylesModel (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; +} + +- (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]; + if ([style scale] < 1.0) + [shapeTrans setScale:[style scale] * [shapeTrans scale]]; + + 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 pixbuf_get_from_surface (surface); +} + +- (void) addNodeStyle:(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, + NODE_STYLES_NAME_COL, [[style name] UTF8String], + NODE_STYLES_ICON_COL, pixbuf, + NODE_STYLES_PTR_COL, (gpointer)[style retain], + -1); + g_object_unref (pixbuf); + [self observeNodeStyle:style]; +} + +- (void) addNodeStyle:(NodeStyle*)style { + cairo_surface_t *surface = [self createNodeIconSurface]; + [self addNodeStyle:style usingSurface:surface]; + cairo_surface_destroy (surface); +} + +- (void) observeNodeStyle:(NodeStyle*)style { + [style addObserver:self + forKeyPath:@"name" + options:NSKeyValueObservingOptionNew + context:NULL]; + [style addObserver:self + forKeyPath:@"strokeThickness" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"strokeColorRGB.red" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"strokeColorRGB.green" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"strokeColorRGB.blue" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"fillColorRGB.red" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"fillColorRGB.green" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"fillColorRGB.blue" + options:0 + context:NULL]; + [style addObserver:self + forKeyPath:@"shapeName" + options:0 + context:NULL]; +} + +- (void) stopObservingNodeStyle:(NodeStyle*)style { + [style removeObserver:self forKeyPath:@"name"]; + [style removeObserver:self forKeyPath:@"strokeThickness"]; + [style removeObserver:self forKeyPath:@"strokeColorRGB.red"]; + [style removeObserver:self forKeyPath:@"strokeColorRGB.green"]; + [style removeObserver:self forKeyPath:@"strokeColorRGB.blue"]; + [style removeObserver:self forKeyPath:@"fillColorRGB.red"]; + [style removeObserver:self forKeyPath:@"fillColorRGB.green"]; + [style removeObserver:self forKeyPath:@"fillColorRGB.blue"]; + [style removeObserver:self forKeyPath:@"shapeName"]; +} + +- (void) clearNodeStylesModel { + 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, NODE_STYLES_PTR_COL, &rowStyle, -1); + [self stopObservingNodeStyle:rowStyle]; + [rowStyle release]; + } while (gtk_tree_model_iter_next (model, &row)); + } + gtk_list_store_clear (store); +} + +- (void) reloadNodeStyles { + [self clearNodeStylesModel]; + cairo_surface_t *surface = [self createNodeIconSurface]; + for (NodeStyle *style in [styleManager nodeStyles]) { + [self addNodeStyle:style usingSurface:surface]; + } + cairo_surface_destroy (surface); +} +@end + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/NodeStylesPalette.h b/tikzit-1/src/gtk/NodeStylesPalette.h new file mode 100644 index 0000000..ac712ea --- /dev/null +++ b/tikzit-1/src/gtk/NodeStylesPalette.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 StyleManager; +@class NodeStyleSelector; +@class NodeStyleEditor; + +@interface NodeStylesPalette: NSObject { + NodeStyleSelector *selector; + NodeStyleEditor *editor; + + GtkWidget *palette; + + GtkWidget *removeStyleButton; + GtkWidget *applyStyleButton; + GtkWidget *clearStyleButton; +} + +@property (retain) StyleManager *styleManager; +@property (readonly) GtkWidget *widget; + +- (id) initWithManager:(StyleManager*)m; + +@end + +// vim:ft=objc:ts=4:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/NodeStylesPalette.m b/tikzit-1/src/gtk/NodeStylesPalette.m new file mode 100644 index 0000000..e28edbb --- /dev/null +++ b/tikzit-1/src/gtk/NodeStylesPalette.m @@ -0,0 +1,197 @@ +/* + * 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 "NodeStylesModel.h" +#import "StyleManager.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); +// }}} +// {{{ Notifications + +@interface NodeStylesPalette (Notifications) +- (void) selectedStyleChanged:(NSNotification*)notification; +@end + +// }}} +// {{{ Private + +@interface NodeStylesPalette (Private) +- (void) updateButtonState; +- (void) removeSelectedStyle; +- (void) addStyle; +@end + +// }}} +// }}} +// {{{ API + +@implementation NodeStylesPalette + +@synthesize widget=palette; + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithManager:(StyleManager*)m { + self = [super init]; + + if (self) { + selector = [[NodeStyleSelector alloc] initWithStyleManager:m]; + editor = [[NodeStyleEditor alloc] init]; + + palette = gtk_vbox_new (FALSE, 6); + gtk_container_set_border_width (GTK_CONTAINER (palette), 6); + g_object_ref_sink (palette); + + GtkWidget *mainBox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (palette), mainBox, FALSE, FALSE, 0); + gtk_widget_show (mainBox); + + GtkWidget *selectorScroller = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (selectorScroller), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + GtkWidget *selectorFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (selectorScroller), [selector widget]); + gtk_container_add (GTK_CONTAINER (selectorFrame), selectorScroller); + gtk_box_pack_start (GTK_BOX (mainBox), selectorFrame, TRUE, TRUE, 0); + gtk_widget_show (selectorScroller); + gtk_widget_show (selectorFrame); + gtk_widget_show ([selector widget]); + + gtk_box_pack_start (GTK_BOX (mainBox), [editor widget], TRUE, TRUE, 0); + gtk_widget_show ([editor widget]); + + GtkBox *buttonBox = GTK_BOX (gtk_hbox_new(FALSE, 0)); + gtk_box_pack_start (GTK_BOX (palette), GTK_WIDGET (buttonBox), 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 (buttonBox, 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 (buttonBox, removeStyleButton, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (removeStyleButton), + "clicked", + G_CALLBACK (remove_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 model] styleManager]; +} + +- (void) setStyleManager:(StyleManager*)m { + [[selector model] setStyleManager:m]; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [editor release]; + [selector release]; + g_object_unref (palette); + g_object_unref (removeStyleButton); + + [super dealloc]; +} + +@end + +// }}} +// {{{ Notifications + +@implementation NodeStylesPalette (Notifications) +- (void) selectedStyleChanged:(NSNotification*)notification { + [editor setStyle:[selector selectedStyle]]; + [self updateButtonState]; +} +@end + +// }}} +// {{{ Private + +@implementation NodeStylesPalette (Private) +- (void) updateButtonState { + gboolean hasStyleSelection = [selector selectedStyle] != nil; + + gtk_widget_set_sensitive (removeStyleButton, hasStyleSelection); +} + +- (void) removeSelectedStyle { + NodeStyle *style = [selector selectedStyle]; + if (style) + [[[selector model] styleManager] removeNodeStyle:style]; +} + +- (void) addStyle { + NodeStyle *newStyle = [NodeStyle defaultNodeStyleWithName:@"newstyle"]; + [[self styleManager] addNodeStyle:newStyle]; + [selector setSelectedStyle:newStyle]; + [editor selectNameField]; +} + +@end + +// }}} +// {{{ GTK+ callbacks + +static void add_style_button_cb (GtkButton *widget, NodeStylesPalette *palette) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [palette addStyle]; + [pool drain]; +} + +static void remove_style_button_cb (GtkButton *widget, NodeStylesPalette *palette) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [palette removeSelectedStyle]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/PreambleEditor.h b/tikzit-1/src/gtk/PreambleEditor.h new file mode 100644 index 0000000..f181446 --- /dev/null +++ b/tikzit-1/src/gtk/PreambleEditor.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 "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) present; +- (void) show; +- (void) hide; +- (BOOL) isVisible; +- (void) setVisible:(BOOL)visible; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/PreambleEditor.m b/tikzit-1/src/gtk/PreambleEditor.m new file mode 100644 index 0000000..d1f72ee --- /dev/null +++ b/tikzit-1/src/gtk/PreambleEditor.m @@ -0,0 +1,568 @@ +/* + * 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 "Application.h" +#import "Preambles.h" +#import <gdk/gdk.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 gboolean window_focus_out_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; +- (void) nodeStylePropertyChanged:(NSNotification*)notification; +- (void) edgeStylePropertyChanged:(NSNotification*)notification; +@end + +// }}} +// {{{ API + +@implementation PreambleEditor + +- (id) init { + [self release]; + 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; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(nodeStylePropertyChanged:) + name:@"NodeStylePropertyChanged" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(edgeStylePropertyChanged:) + name:@"EdgeStylePropertyChanged" + object:nil]; + } + + return self; +} + +- (Preambles*) preambles { + return preambles; +} + +- (void) setParentWindow:(GtkWindow*)parent { + GtkWindow *oldParent = parentWindow; + + if (parentWindow) + g_object_ref (parentWindow); + parentWindow = parent; + if (oldParent) + g_object_unref (oldParent); + + if (window) { + gtk_window_set_transient_for (window, parentWindow); + } +} + +- (void) present { + [self loadUi]; + gtk_window_present (GTK_WINDOW (window)); + [self revert]; +} + +- (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; + if (window) { + gtk_widget_destroy (GTK_WIDGET (window)); + window = NULL; + } + if (parentWindow) { + g_object_ref (parentWindow); + } + + [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_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); + } + GdkEventMask mask; + g_object_get (G_OBJECT (window), "events", &mask, NULL); + mask |= GDK_FOCUS_CHANGE_MASK; + g_object_set (G_OBJECT (window), "events", mask, NULL); + g_signal_connect (window, + "delete-event", + G_CALLBACK (window_delete_event_cb), + self); + g_signal_connect (window, + "focus-out-event", + G_CALLBACK (window_focus_out_event_cb), + self); + + GtkWidget *mainBox = gtk_vbox_new (FALSE, 18); + gtk_container_set_border_width (GTK_CONTAINER (mainBox), 12); + 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]; + GtkWidget *listFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (listFrame), listWidget); + + GtkBox *listBox = GTK_BOX (gtk_vbox_new (FALSE, 6)); + gtk_box_pack_start (listBox, listFrame, TRUE, TRUE, 0); + + GtkContainer *listButtonBox = GTK_CONTAINER (gtk_hbox_new (FALSE, 6)); + 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 ()); + gtk_text_view_set_left_margin (preambleView, 3); + gtk_text_view_set_right_margin (preambleView, 3); + 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)); + GtkWidget *editorFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (editorFrame), scroller); + gtk_paned_pack2 (paned, editorFrame, TRUE, TRUE); + + GtkContainer *buttonBox = GTK_CONTAINER (gtk_hbutton_box_new ()); + gtk_box_set_spacing (GTK_BOX (buttonBox), 6); + 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 (!preambleView) + return; + 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]; + [app saveConfiguration]; +} + +- (void) revert { + if (!preambleView) + return; + 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); + } +} + +- (void) nodeStylePropertyChanged:(NSNotification*)notification { + if ([self isDefaultPreambleSelected]) { + [self revert]; + } +} + +- (void) edgeStylePropertyChanged:(NSNotification*)notification { + if ([self isDefaultPreambleSelected]) { + [self revert]; + } +} +@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 gboolean window_focus_out_event_cb (GtkWidget *widget, + GdkEvent *event, + PreambleEditor *editor) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [editor save]; + [pool drain]; + return FALSE; +} + +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:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/Preambles+Storage.h b/tikzit-1/src/gtk/Preambles+Storage.h new file mode 100644 index 0000000..76f56cc --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/Preambles+Storage.m b/tikzit-1/src/gtk/Preambles+Storage.m new file mode 100644 index 0000000..bd3ea03 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/PreviewRenderer.h b/tikzit-1/src/gtk/PreviewRenderer.h new file mode 100644 index 0000000..d691722 --- /dev/null +++ b/tikzit-1/src/gtk/PreviewRenderer.h @@ -0,0 +1,48 @@ +/* + * 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 "Surface.h" + +@class Configuration; +@class Preambles; +@class TikzDocument; + +@interface PreviewRenderer: NSObject<RenderDelegate> { + Configuration *config; + Preambles *preambles; + TikzDocument *document; + PopplerDocument *pdfDocument; + PopplerPage *pdfPage; +} + +@property (readonly) Preambles *preambles; +@property (retain) TikzDocument *document; +@property (readonly) double height; +@property (readonly) double width; + +- (id) initWithPreambles:(Preambles*)p config:(Configuration*)c; + +- (BOOL) updateWithError:(NSError**)error; +- (BOOL) update; +- (BOOL) isValid; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/PreviewRenderer.m b/tikzit-1/src/gtk/PreviewRenderer.m new file mode 100644 index 0000000..28113d6 --- /dev/null +++ b/tikzit-1/src/gtk/PreviewRenderer.m @@ -0,0 +1,250 @@ +/* + * 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" +#import "Configuration.h" +#import "Preambles.h" +#import "TikzDocument.h" + +@implementation PreviewRenderer + +@synthesize preambles, document; + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithPreambles:(Preambles*)p config:(Configuration*)c { + self = [super init]; + + if (self) { + document = nil; + config = [c retain]; + preambles = [p retain]; + pdfDocument = NULL; + pdfPage = NULL; + } + + return self; +} + +- (void) dealloc { + [document release]; + [config release]; + [preambles release]; + + if (pdfDocument) { + g_object_unref (pdfDocument); + pdfDocument = NULL; + } + if (pdfPage) { + g_object_unref (pdfPage); + pdfPage = NULL; + } + + [super dealloc]; +} + +- (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 = [preambles buildDocumentForTikz:[document tikz]]; + + 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]; + + NSTask *latexTask = [[NSTask alloc] init]; + [latexTask setCurrentDirectoryPath:tempDir]; + + // GNUStep is clever enough to use PATH + NSString *path = [config stringEntry:@"pdflatex" + inGroup:@"Previews" + withDefault:@"pdflatex"]; + [latexTask setLaunchPath:path]; + + NSArray *args = [NSArray arrayWithObjects: + @"-fmt=latex", + @"-output-format=pdf", + @"-interaction=nonstopmode", + @"-halt-on-error", + texFile, + nil]; + [latexTask setArguments:args]; + + NSPipe *pout = [NSPipe pipe]; + [latexTask setStandardOutput:pout]; + + NSFileHandle *latexOut = [pout fileHandleForReading]; + + BOOL success = NO; + + NS_DURING { + [latexTask launch]; + [latexTask waitUntilExit]; + } NS_HANDLER { + NSLog(@"Failed to run '%@'; error was: %@", path, [localException reason]); + (void)localException; + if (error) { + NSString *desc = [NSString stringWithFormat:@"Failed to run '%@'", path]; + NSMutableDictionary *errorDetail = [NSMutableDictionary dictionaryWithCapacity:2]; + [errorDetail setValue:desc forKey:NSLocalizedDescriptionKey]; + *error = [NSError errorWithDomain:TZErrorDomain code:TZ_ERR_IO userInfo:errorDetail]; + } + + // remove all temporary files + [[NSFileManager defaultManager] removeFileAtPath:tempDir handler:NULL]; + [latexTask release]; + + return NO; + } NS_ENDHANDLER + + if ([latexTask terminationStatus] != 0) { + if (error) { + NSData *data = [latexOut readDataToEndOfFile]; + NSString *str = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + 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]; + [str release]; + } + } 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]; + [latexTask release]; + + return success; +} + +- (BOOL) isValid { + return pdfPage ? YES : NO; +} + +- (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 && pdfPage) { + CairoRenderContext *context = (CairoRenderContext*)c; + + double w = 0.0; + double h = 0.0; + poppler_page_get_size(pdfPage, &w, &h); + if (w==0) w = 1.0; + if (h==0) h = 1.0; + + double scale = ([surface height] / h) * 0.95; + if (w * scale > [surface width]) scale = [surface width] / w; + [[surface transformer] setScale:scale]; + + NSPoint origin; + w *= scale; + h *= scale; + origin.x = ([surface width] - w) / 2; + origin.y = ([surface height] - h) / 2; + + [[surface transformer] setOrigin:origin]; + + [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-1/src/gtk/PreviewWindow.h b/tikzit-1/src/gtk/PreviewWindow.h new file mode 100644 index 0000000..8bcd3e5 --- /dev/null +++ b/tikzit-1/src/gtk/PreviewWindow.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 "TZFoundation.h" +#import <gtk/gtk.h> + +@class Configuration; +@class Preambles; +@class PreviewRenderer; +@class TikzDocument; +@class WidgetSurface; + +@interface PreviewWindow: NSObject { + PreviewRenderer *previewer; + GtkWindow *window; + WidgetSurface *surface; + GtkWindow *parent; +} + +- (id) initWithPreambles:(Preambles*)p config:(Configuration*)c; + +- (void) setParentWindow:(GtkWindow*)parent; + +- (TikzDocument*) document; +- (void) setDocument:(TikzDocument*)doc; + +- (BOOL) update; + +- (void) present; +- (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-1/src/gtk/PreviewWindow.m b/tikzit-1/src/gtk/PreviewWindow.m new file mode 100644 index 0000000..fc0e7a3 --- /dev/null +++ b/tikzit-1/src/gtk/PreviewWindow.m @@ -0,0 +1,195 @@ +/* + * 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; +- (void) updateDefaultSize; +@end + +// {{{ API + +@implementation PreviewWindow + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithPreambles:(Preambles*)p config:(Configuration*)c { + self = [super init]; + + if (self) { + parent = NULL; + previewer = [[PreviewRenderer alloc] initWithPreambles:p config:c]; + + window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); + gtk_window_set_title (window, "Preview"); + gtk_window_set_resizable (window, TRUE); + gtk_window_set_default_size (window, 150.0, 150.0); + 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_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) present { + if ([self updateOrShowError]) { + [self updateDefaultSize]; + gtk_window_present (GTK_WINDOW (window)); + [surface invalidate]; + } +} + +- (BOOL) update { + if ([self updateOrShowError]) { + [self updateDefaultSize]; + return YES; + } + + return NO; +} + +- (void) show { + if ([self updateOrShowError]) { + [self updateDefaultSize]; + 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 { + gtk_widget_destroy (GTK_WIDGET (window)); + [previewer release]; + [surface release]; + + [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; +} + +- (void) updateDefaultSize { + 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; + } + gtk_window_set_default_size (window, width, height); +} +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/PropertiesPane.h b/tikzit-1/src/gtk/PropertiesPane.h new file mode 100644 index 0000000..c76efae --- /dev/null +++ b/tikzit-1/src/gtk/PropertiesPane.h @@ -0,0 +1,69 @@ +/* + * Copyright 2011-2012 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 Configuration; +@class EdgePropertyDelegate; +@class EdgeStylesModel; +@class GraphPropertyDelegate; +@class NodePropertyDelegate; +@class NodeStylesModel; +@class PropertyListEditor; +@class StyleManager; +@class TikzDocument; + +@interface PropertiesPane: NSObject { + TikzDocument *document; + BOOL blockUpdates; + + PropertyListEditor *graphProps; + PropertyListEditor *nodeProps; + PropertyListEditor *edgeProps; + PropertyListEditor *edgeNodeProps; + + GraphPropertyDelegate *graphPropDelegate; + NodePropertyDelegate *nodePropDelegate; + EdgePropertyDelegate *edgePropDelegate; + + GtkWidget *layout; + + GtkWidget *currentPropsWidget; // no ref! + + GtkWidget *graphPropsWidget; + GtkWidget *nodePropsWidget; + GtkWidget *edgePropsWidget; + + GtkEntry *nodeLabelEntry; + GtkToggleButton *edgeNodeToggle; + GtkWidget *edgeNodePropsWidget; + GtkEntry *edgeNodeLabelEntry; + GtkEntry *edgeSourceAnchorEntry; + GtkEntry *edgeTargetAnchorEntry; +} + +@property (retain) TikzDocument *document; +@property (assign) BOOL visible; +@property (readonly) GtkWidget *gtkWidget; + +- (void) loadConfiguration:(Configuration*)config; +- (void) saveConfiguration:(Configuration*)config; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/PropertiesPane.m b/tikzit-1/src/gtk/PropertiesPane.m new file mode 100644 index 0000000..ba43298 --- /dev/null +++ b/tikzit-1/src/gtk/PropertiesPane.m @@ -0,0 +1,763 @@ +/* + * Copyright 2011-2012 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 "PropertiesPane.h" + +#import "GraphElementProperty.h" +#import "PropertyListEditor.h" +#import "TikzDocument.h" + +#import "gtkhelpers.h" + +// {{{ Internal interfaces +// {{{ GTK+ helpers +static GtkWidget *createLabelledEntry (const gchar *labelText, GtkEntry **entry); +static GtkWidget *createPropsPaneWithLabelEntry (PropertyListEditor *props, GtkEntry **labelEntry); +static GtkWidget *createBoldLabel (const gchar *text); +// }}} +// {{{ GTK+ callbacks +static void node_label_changed_cb (GtkEditable *widget, PropertiesPane *pane); +static void edge_node_label_changed_cb (GtkEditable *widget, PropertiesPane *pane); +static void edge_node_toggled_cb (GtkToggleButton *widget, PropertiesPane *pane); +static void edge_source_anchor_changed_cb (GtkEditable *widget, PropertiesPane *pane); +static void edge_target_anchor_changed_cb (GtkEditable *widget, PropertiesPane *pane); +// }}} + +@interface PropertiesPane (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; +- (BOOL) edgeSourceAnchorEdited:(NSString*)newValue; +- (BOOL) edgeTargetAnchorEdited:(NSString*)newValue; +@end + +@interface PropertiesPane (Private) +- (void) _updatePane; +- (void) _setDisplayedWidget:(GtkWidget*)widget; +@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 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]; + + layout = gtk_vbox_new (FALSE, 0); + g_object_ref_sink (layout); + gtk_widget_show (layout); + + /* + * Graph properties + */ + graphPropsWidget = gtk_vbox_new (FALSE, 6); + g_object_ref_sink (graphPropsWidget); + gtk_widget_show (graphPropsWidget); + + GtkWidget *label = createBoldLabel ("Graph properties"); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (graphPropsWidget), + label, + FALSE, FALSE, 0); + + gtk_widget_show ([graphProps widget]); + gtk_box_pack_start (GTK_BOX (graphPropsWidget), + [graphProps widget], + TRUE, TRUE, 0); + + gtk_box_pack_start (GTK_BOX (layout), + graphPropsWidget, + TRUE, TRUE, 0); + + + /* + * Node properties + */ + nodePropsWidget = gtk_vbox_new (FALSE, 6); + g_object_ref_sink (nodePropsWidget); + gtk_box_pack_start (GTK_BOX (layout), + nodePropsWidget, + TRUE, TRUE, 0); + + label = createBoldLabel ("Node properties"); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (nodePropsWidget), + label, + FALSE, FALSE, 0); + + GtkWidget *labelWidget = createLabelledEntry ("Label", &nodeLabelEntry); + gtk_widget_show (labelWidget); + gtk_box_pack_start (GTK_BOX (nodePropsWidget), + labelWidget, + FALSE, FALSE, 0); + + gtk_widget_show ([nodeProps widget]); + gtk_box_pack_start (GTK_BOX (nodePropsWidget), + [nodeProps widget], + TRUE, TRUE, 0); + + g_signal_connect (G_OBJECT (nodeLabelEntry), + "changed", + G_CALLBACK (node_label_changed_cb), + self); + + /* + * Edge properties + */ + edgePropsWidget = gtk_vbox_new (FALSE, 6); + g_object_ref_sink (edgePropsWidget); + gtk_box_pack_start (GTK_BOX (layout), + edgePropsWidget, + TRUE, TRUE, 0); + + label = createBoldLabel ("Edge properties"); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (edgePropsWidget), + label, + FALSE, FALSE, 0); + + gtk_widget_show ([edgeProps widget]); + gtk_box_pack_start (GTK_BOX (edgePropsWidget), + [edgeProps widget], + TRUE, TRUE, 0); + + GtkWidget *split = gtk_hseparator_new (); + gtk_widget_show (split); + gtk_box_pack_start (GTK_BOX (edgePropsWidget), + split, + FALSE, FALSE, 0); + + GtkWidget *anchorTable = gtk_table_new (2, 2, FALSE); + + label = gtk_label_new ("Source anchor:"); + gtk_table_attach_defaults (GTK_TABLE (anchorTable), label, + 0, 1, 0, 1); + edgeSourceAnchorEntry = GTK_ENTRY (gtk_entry_new ()); + g_object_ref_sink (edgeSourceAnchorEntry); + gtk_table_attach_defaults (GTK_TABLE (anchorTable), + GTK_WIDGET (edgeSourceAnchorEntry), + 1, 2, 0, 1); + g_signal_connect (G_OBJECT (edgeSourceAnchorEntry), + "changed", + G_CALLBACK (edge_source_anchor_changed_cb), + self); + + label = gtk_label_new ("Target anchor:"); + gtk_table_attach_defaults (GTK_TABLE (anchorTable), label, + 0, 1, 1, 2); + edgeTargetAnchorEntry = GTK_ENTRY (gtk_entry_new ()); + g_object_ref_sink (edgeTargetAnchorEntry); + gtk_table_attach_defaults (GTK_TABLE (anchorTable), + GTK_WIDGET (edgeTargetAnchorEntry), + 1, 2, 1, 2); + g_signal_connect (G_OBJECT (edgeTargetAnchorEntry), + "changed", + G_CALLBACK (edge_target_anchor_changed_cb), + self); + + gtk_widget_show_all (anchorTable); + gtk_box_pack_start (GTK_BOX (edgePropsWidget), + anchorTable, + FALSE, FALSE, 0); + + split = gtk_hseparator_new (); + gtk_widget_show (split); + gtk_box_pack_start (GTK_BOX (edgePropsWidget), + split, + FALSE, FALSE, 0); + + edgeNodeToggle = GTK_TOGGLE_BUTTON (gtk_check_button_new_with_label ("Child node")); + g_object_ref_sink (edgeNodeToggle); + gtk_widget_show (GTK_WIDGET (edgeNodeToggle)); + gtk_box_pack_start (GTK_BOX (edgePropsWidget), + GTK_WIDGET (edgeNodeToggle), + FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (GTK_WIDGET (edgeNodeToggle)), + "toggled", + G_CALLBACK (edge_node_toggled_cb), + self); + + edgeNodePropsWidget = createPropsPaneWithLabelEntry(edgeNodeProps, &edgeNodeLabelEntry); + g_object_ref_sink (edgeNodePropsWidget); + g_object_ref_sink (edgeNodeLabelEntry); + gtk_box_pack_start (GTK_BOX (edgePropsWidget), + edgeNodePropsWidget, + TRUE, TRUE, 0); + g_signal_connect (G_OBJECT (edgeNodeLabelEntry), + "changed", + G_CALLBACK (edge_node_label_changed_cb), + self); + + /* + * Misc setup + */ + + [self _setDisplayedWidget:graphPropsWidget]; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + g_object_unref (graphPropsWidget); + g_object_unref (nodePropsWidget); + g_object_unref (edgePropsWidget); + + g_object_unref (nodeLabelEntry); + g_object_unref (edgeNodeToggle); + g_object_unref (edgeNodePropsWidget); + g_object_unref (edgeNodeLabelEntry); + g_object_unref (edgeSourceAnchorEntry); + g_object_unref (edgeTargetAnchorEntry); + + g_object_unref (layout); + + [graphProps release]; + [nodeProps release]; + [edgeProps release]; + [edgeNodeProps release]; + + [graphPropDelegate release]; + [nodePropDelegate release]; + [edgePropDelegate release]; + + [document 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]]; + } + + [doc retain]; + [document release]; + document = doc; + + [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 _updatePane]; +} + +- (BOOL) visible { + return gtk_widget_get_visible (layout); +} + +- (void) setVisible:(BOOL)visible { + gtk_widget_set_visible (layout, visible); +} + +- (GtkWidget*) gtkWidget { + return layout; +} + +- (void) loadConfiguration:(Configuration*)config { +} + +- (void) saveConfiguration:(Configuration*)config { +} + +@end +// }}} +// {{{ Notifications + +@implementation PropertiesPane (Notifications) + +- (void) nodeSelectionChanged:(NSNotification*)n { + [self _updatePane]; +} + +- (void) edgeSelectionChanged:(NSNotification*)n { + [self _updatePane]; +} + +- (void) graphChanged:(NSNotification*)n { + [self _updatePane]; +} + +- (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; + } + + if ([newValue isValidTikzPropertyNameOrValue]) { + Node *node = [sel anyObject]; + [document startModifyNode:node]; + [node setLabel:newValue]; + [document endModifyNode]; + } else { + widget_set_error (GTK_WIDGET (nodeLabelEntry)); + } +} + +- (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; + } + + if ([newValue isValidTikzPropertyNameOrValue]) { + [document startModifyEdge:edge]; + [[edge edgeNode] setLabel:newValue]; + [document endModifyEdge]; + } else { + widget_set_error (GTK_WIDGET (edgeNodeLabelEntry)); + } +} + +- (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]; +} + +- (BOOL) edgeSourceAnchorEdited:(NSString*)newValue { + if (blockUpdates) + return YES; + + NSSet *sel = [[document pickSupport] selectedEdges]; + if ([sel count] != 1) { + NSLog(@"Expected single edge selected; got %lu", [sel count]); + return YES; + } + + Edge *edge = [sel anyObject]; + if ([newValue isValidAnchor]) { + [document startModifyEdge:edge]; + [edge setSourceAnchor:newValue]; + [document endModifyEdge]; + return YES; + } else { + return NO; + } +} + +- (BOOL) edgeTargetAnchorEdited:(NSString*)newValue { + if (blockUpdates) + return YES; + + NSSet *sel = [[document pickSupport] selectedEdges]; + if ([sel count] != 1) { + NSLog(@"Expected single edge selected; got %lu", [sel count]); + return YES; + } + + Edge *edge = [sel anyObject]; + if ([newValue isValidAnchor]) { + [document startModifyEdge:edge]; + [edge setTargetAnchor:newValue]; + [document endModifyEdge]; + return YES; + } else { + return NO; + } +} + +@end +// }}} +// {{{ Private + +@implementation PropertiesPane (Private) + +- (void) _setDisplayedWidget:(GtkWidget*)widget { + if (currentPropsWidget != widget) { + if (currentPropsWidget) + gtk_widget_hide (currentPropsWidget); + currentPropsWidget = widget; + if (widget) + gtk_widget_show (widget); + } +} + +- (void) _updatePane { + blockUpdates = YES; + + BOOL editGraphProps = YES; + GraphElementData *data = [[document graph] data]; + [graphProps setData:data]; + + NSSet *nodeSel = [[document pickSupport] selectedNodes]; + if ([nodeSel count] == 1) { + Node *n = [nodeSel anyObject]; + [nodePropDelegate setNode:n]; + [nodeProps setData:[n data]]; + gtk_entry_set_text (nodeLabelEntry, [[n label] UTF8String]); + widget_clear_error (GTK_WIDGET (nodeLabelEntry)); + [self _setDisplayedWidget:nodePropsWidget]; + editGraphProps = NO; + } else { + [nodePropDelegate setNode:nil]; + [nodeProps setData:nil]; + gtk_entry_set_text (nodeLabelEntry, ""); + + NSSet *edgeSel = [[document pickSupport] selectedEdges]; + if ([edgeSel count] == 1) { + Edge *e = [edgeSel anyObject]; + [edgePropDelegate setEdge:e]; + [edgeProps setData:[e data]]; + gtk_entry_set_text (edgeSourceAnchorEntry, + [[e sourceAnchor] UTF8String]); + gtk_entry_set_text (edgeTargetAnchorEntry, + [[e targetAnchor] UTF8String]); + widget_clear_error (GTK_WIDGET (edgeSourceAnchorEntry)); + widget_clear_error (GTK_WIDGET (edgeTargetAnchorEntry)); + widget_clear_error (GTK_WIDGET (edgeNodeLabelEntry)); + if ([e hasEdgeNode]) { + gtk_toggle_button_set_active (edgeNodeToggle, TRUE); + gtk_widget_show (edgeNodePropsWidget); + gtk_entry_set_text (edgeNodeLabelEntry, [[[e edgeNode] label] UTF8String]); + [edgeNodeProps setData:[[e edgeNode] data]]; + gtk_widget_set_sensitive (edgeNodePropsWidget, TRUE); + } else { + gtk_toggle_button_set_active (edgeNodeToggle, FALSE); + gtk_widget_hide (edgeNodePropsWidget); + gtk_entry_set_text (edgeNodeLabelEntry, ""); + [edgeNodeProps setData:nil]; + gtk_widget_set_sensitive (edgeNodePropsWidget, FALSE); + } + [self _setDisplayedWidget:edgePropsWidget]; + editGraphProps = NO; + } else { + [edgePropDelegate setEdge:nil]; + [edgeProps setData:nil]; + [edgeNodeProps setData:nil]; + gtk_entry_set_text (edgeNodeLabelEntry, ""); + } + } + + if (editGraphProps) { + [self _setDisplayedWidget:graphPropsWidget]; + } + + blockUpdates = NO; +} + +@end + +// }}} +// {{{ Delegates + +@implementation GraphPropertyDelegate +- (id) init { + self = [super init]; + if (self) { + doc = nil; + } + return self; +} +- (void) dealloc { + // doc is not retained + [super dealloc]; +} +- (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) dealloc { + // doc,node not retained + [super dealloc]; +} +- (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) dealloc { + // doc,edge not retained + [super dealloc]; +} +- (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, TRUE, 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, 6)); + + GtkWidget *labelWidget = createLabelledEntry ("Label", labelEntry); + gtk_widget_show (labelWidget); + // box widget expand fill pad + gtk_box_pack_start (box, labelWidget, FALSE, FALSE, 0); + gtk_box_pack_start (box, [props widget], TRUE, TRUE, 0); + gtk_widget_show ([props widget]); + return GTK_WIDGET (box); +} + +static GtkWidget *createBoldLabel (const gchar *text) { + GtkWidget *label = gtk_label_new (text); + label_set_bold (GTK_LABEL (label)); + return label; +} + +// }}} +// {{{ GTK+ callbacks + +static void node_label_changed_cb (GtkEditable *editable, PropertiesPane *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, PropertiesPane *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, PropertiesPane *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_active (toggle); + [pane edgeNodeToggled:newValue]; + + [pool drain]; +} + +static void edge_source_anchor_changed_cb (GtkEditable *editable, PropertiesPane *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); + if (![pane edgeSourceAnchorEdited:newValue]) + widget_set_error (GTK_WIDGET (editable)); + + [pool drain]; +} + +static void edge_target_anchor_changed_cb (GtkEditable *editable, PropertiesPane *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); + if (![pane edgeTargetAnchorEdited:newValue]) + widget_set_error (GTK_WIDGET (editable)); + + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/PropertyListEditor.h b/tikzit-1/src/gtk/PropertyListEditor.h new file mode 100644 index 0000000..2d3166a --- /dev/null +++ b/tikzit-1/src/gtk/PropertyListEditor.h @@ -0,0 +1,65 @@ +/* + * 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 { + GraphElementData *data; + NSObject<PropertyChangeDelegate> *delegate; + + GtkListStore *list; + GtkWidget *view; + GtkWidget *widget; + GtkWidget *removeButton; +} + +/*! + @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-1/src/gtk/PropertyListEditor.m b/tikzit-1/src/gtk/PropertyListEditor.m new file mode 100644 index 0000000..9760618 --- /dev/null +++ b/tikzit-1/src/gtk/PropertyListEditor.m @@ -0,0 +1,501 @@ +/* + * 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" +#import "gtkhelpers.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); +static void text_editing_started (GtkCellRenderer *cell, + GtkCellEditable *editable, + const gchar *path, + PropertyListEditor *editor); +static void text_changed_cb (GtkEditable *editable, + PropertyListEditor *pane); +static void selection_changed_cb (GtkTreeSelection *selection, + PropertyListEditor *pane); + +// }}} +// {{{ Private + +@interface PropertyListEditor (Private) +- (void) updatePath:(gchar*)path withValue:(NSString*)newText; +- (void) updatePath:(gchar*)path withName:(NSString*)newText; +- (void) addProperty; +- (void) addAtom; +- (void) removeSelected; +- (void) selectionCountChanged:(int)newSelectionCount; +- (void) clearStore; +@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); + g_object_ref_sink (G_OBJECT (list)); + view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (list)); + g_object_ref_sink (G_OBJECT (view)); + 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), + "editing-started", + G_CALLBACK (text_editing_started), + self); + 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); + gtk_box_set_spacing (GTK_BOX (widget), 6); + g_object_ref_sink (G_OBJECT (widget)); + + GtkWidget *listFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (listFrame), scrolledview); + gtk_widget_show (listFrame); + gtk_container_add (GTK_CONTAINER (widget), listFrame); + + 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); + + removeButton = gtk_button_new (); + g_object_ref_sink (G_OBJECT (removeButton)); + gtk_widget_set_sensitive (removeButton, FALSE); + //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); + + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + g_signal_connect (G_OBJECT (selection), + "changed", + G_CALLBACK (selection_changed_cb), + self); + + gtk_widget_show_all (GTK_WIDGET (buttonBox)); + gtk_widget_show_all (scrolledview); + + gtk_widget_set_sensitive (widget, FALSE); + } + + return self; +} + +- (void) dealloc { + [self clearStore]; + + [data release]; + [delegate release]; + + g_object_unref (list); + g_object_unref (view); + g_object_unref (widget); + g_object_unref (removeButton); + + [super dealloc]; +} + +@synthesize widget, delegate; + +- (GraphElementData*) data { return data; } +- (void) setData:(GraphElementData*)d { + [d retain]; + [data release]; + data = d; + [self reloadProperties]; + gtk_widget_set_sensitive (widget, data != nil); +} + +- (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; + } +} + +@end + +// }}} +// {{{ Private + +@implementation PropertyListEditor (Private) +- (void) updatePath:(gchar*)pathStr withValue:(NSString*)newText { + if (![newText isValidTikzPropertyNameOrValue]) + return; + + 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 { + if (![newText isValidTikzPropertyNameOrValue]) + return; + + 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); +} + +- (void) selectionCountChanged:(int)count { + gtk_widget_set_sensitive (removeButton, count > 0); +} + +- (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); + } +} +@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]; +} + +static void text_editing_started (GtkCellRenderer *cell, + GtkCellEditable *editable, + const gchar *path, + PropertyListEditor *editor) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + if (GTK_IS_EDITABLE (editable) && GTK_IS_WIDGET (editable)) { + g_signal_handlers_disconnect_by_func (G_OBJECT (editable), + G_CALLBACK (text_changed_cb), + editor); + widget_clear_error (GTK_WIDGET (editable)); + g_signal_connect (G_OBJECT (editable), + "changed", + G_CALLBACK (text_changed_cb), + editor); + } + + [pool drain]; +} + +static void text_changed_cb (GtkEditable *editable, PropertyListEditor *pane) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSString *newValue = gtk_editable_get_string (editable, 0, -1); + if (![newValue isValidTikzPropertyNameOrValue]) { + widget_set_error (GTK_WIDGET (editable)); + } else { + widget_clear_error (GTK_WIDGET (editable)); + } + + [pool drain]; +} + +static void selection_changed_cb (GtkTreeSelection *selection, + PropertyListEditor *pane) +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + int selcount = gtk_tree_selection_count_selected_rows (selection); + [pane selectionCountChanged:selcount]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/RecentManager.h b/tikzit-1/src/gtk/RecentManager.h new file mode 100644 index 0000000..e2c2793 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/RecentManager.m b/tikzit-1/src/gtk/RecentManager.m new file mode 100644 index 0000000..c6074c6 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/SelectTool.h b/tikzit-1/src/gtk/SelectTool.h new file mode 100644 index 0000000..65f511a --- /dev/null +++ b/tikzit-1/src/gtk/SelectTool.h @@ -0,0 +1,63 @@ +/* + * 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 "TZFoundation.h" +#import "Tool.h" + +@class Edge; +@class Node; + +// FIXME: replace this with delegates +typedef enum { + QuietState, + SelectBoxState, + ToggleSelectState, + MoveSelectedNodesState, + DragEdgeControlPoint1, + DragEdgeControlPoint2 +} SelectToolState; + +typedef enum { + DragSelectsNodes = 1, + DragSelectsEdges = 2, + DragSelectsBoth = DragSelectsNodes | DragSelectsEdges +} DragSelectMode; + +@interface SelectTool : NSObject <Tool> { + GraphRenderer *renderer; + SelectToolState state; + float edgeFuzz; + DragSelectMode dragSelectMode; + NSPoint dragOrigin; + Node *leaderNode; + NSPoint oldLeaderPos; + Edge *modifyEdge; + NSRect selectionBox; + NSMutableSet *selectionBoxContents; + + GtkWidget *configWidget; + GSList *dragSelectModeButtons; +} + +@property (assign) float edgeFuzz; +@property (assign) DragSelectMode dragSelectMode; + +- (id) init; ++ (id) tool; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/SelectTool.m b/tikzit-1/src/gtk/SelectTool.m new file mode 100644 index 0000000..b3121ae --- /dev/null +++ b/tikzit-1/src/gtk/SelectTool.m @@ -0,0 +1,590 @@ +/* + * 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 "SelectTool.h" + +#import "Configuration.h" +#import "Edge+Render.h" +#import "GraphRenderer.h" +#import "TikzDocument.h" +#import "tzstockitems.h" + +#import <gdk/gdkkeysyms.h> + +#define DRAG_SELECT_MODE_KEY "tikzit-drag-select-mode" + +static const InputMask unionSelectMask = ShiftMask; + +static void drag_select_mode_cb (GtkToggleButton *button, SelectTool *tool); + +@interface SelectTool (Private) +- (TikzDocument*) doc; +- (void) shiftNodesByMovingLeader:(Node*)leader to:(NSPoint)to; +- (void) deselectAllNodes; +- (void) deselectAllEdges; +- (void) deselectAll; +- (BOOL) circleWithCenter:(NSPoint)c andRadius:(float)r containsPoint:(NSPoint)p; +- (void) lookForControlPointAt:(NSPoint)pos; +- (void) setSelectionBox:(NSRect)box; +- (void) clearSelectionBox; +- (BOOL) selectionBoxContainsNode:(Node*)node; +@end + +@implementation SelectTool +- (NSString*) name { return @"Select"; } +- (const gchar*) stockId { return TIKZIT_STOCK_SELECT; } +- (NSString*) helpText { return @"Select, move and edit nodes and edges"; } +- (NSString*) shortcut { return @"s"; } +@synthesize configurationWidget=configWidget; +@synthesize edgeFuzz; + ++ (id) tool { + return [[[self alloc] init] autorelease]; +} + +- (id) init { + self = [super init]; + + if (self) { + state = QuietState; + edgeFuzz = 3.0f; + dragSelectMode = DragSelectsNodes; + + configWidget = gtk_vbox_new (FALSE, 0); + g_object_ref_sink (configWidget); + + GtkWidget *label = gtk_label_new ("Drag selects:"); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (configWidget), + label, + FALSE, + FALSE, + 0); + + GtkWidget *nodeOpt = gtk_radio_button_new_with_label (NULL, "nodes (N)"); + g_object_set_data (G_OBJECT (nodeOpt), + DRAG_SELECT_MODE_KEY, + (gpointer)DragSelectsNodes); + gtk_box_pack_start (GTK_BOX (configWidget), + nodeOpt, + FALSE, + FALSE, + 0); + g_signal_connect (G_OBJECT (nodeOpt), + "toggled", + G_CALLBACK (drag_select_mode_cb), + self); + + GtkWidget *edgeOpt = gtk_radio_button_new_with_label ( + gtk_radio_button_get_group (GTK_RADIO_BUTTON (nodeOpt)), + "edges (E)"); + g_object_set_data (G_OBJECT (edgeOpt), + DRAG_SELECT_MODE_KEY, + (gpointer)DragSelectsEdges); + gtk_box_pack_start (GTK_BOX (configWidget), + edgeOpt, + FALSE, + FALSE, + 0); + g_signal_connect (G_OBJECT (edgeOpt), + "toggled", + G_CALLBACK (drag_select_mode_cb), + self); + + GtkWidget *bothOpt = gtk_radio_button_new_with_label ( + gtk_radio_button_get_group (GTK_RADIO_BUTTON (edgeOpt)), + "both (B)"); + g_object_set_data (G_OBJECT (bothOpt), + DRAG_SELECT_MODE_KEY, + (gpointer)DragSelectsBoth); + gtk_box_pack_start (GTK_BOX (configWidget), + bothOpt, + FALSE, + FALSE, + 0); + g_signal_connect (G_OBJECT (bothOpt), + "toggled", + G_CALLBACK (drag_select_mode_cb), + self); + dragSelectModeButtons = gtk_radio_button_get_group (GTK_RADIO_BUTTON (bothOpt)); + + gtk_widget_show_all (configWidget); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + [renderer release]; + [leaderNode release]; + [modifyEdge release]; + + g_object_unref (G_OBJECT (configWidget)); + + [super dealloc]; +} + +- (DragSelectMode) dragSelectMode { + return dragSelectMode; +} + +- (void) setDragSelectMode:(DragSelectMode)mode { + if (dragSelectMode == mode) + return; + + dragSelectMode = mode; + + GSList *entry = dragSelectModeButtons; + while (entry) { + GtkToggleButton *button = GTK_TOGGLE_BUTTON (entry->data); + DragSelectMode buttonMode = + (DragSelectMode) g_object_get_data ( + G_OBJECT (button), + DRAG_SELECT_MODE_KEY); + if (buttonMode == dragSelectMode) { + gtk_toggle_button_set_active (button, TRUE); + break; + } + + entry = g_slist_next (entry); + } +} + +- (GraphRenderer*) activeRenderer { return renderer; } +- (void) setActiveRenderer:(GraphRenderer*)r { + if (r == renderer) + return; + + [r retain]; + [renderer release]; + renderer = r; + + state = QuietState; +} + +- (void) mousePressAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + + dragOrigin = pos; + + // we should already be in a quiet state, but no harm in making sure + state = QuietState; + + 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 deselectAll]; + } + 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]; + } + [renderer clearHighlightedNodes]; + state = SelectBoxState; + } + } + } +} + +- (void) mouseMoveTo:(NSPoint)pos withButtons:(MouseButton)buttons andMask:(InputMask)mask { + if (!(buttons & LeftButton)) + return; + + Transformer *transformer = [renderer transformer]; + + if (state == ToggleSelectState) { + state = MoveSelectedNodesState; + oldLeaderPos = [leaderNode point]; + [[self doc] startShiftNodes:[[[self doc] pickSupport] selectedNodes]]; + } + + if (state == SelectBoxState) { + [self setSelectionBox:NSRectAroundPoints(dragOrigin, pos)]; + + NSEnumerator *enumerator = [[self doc] nodeEnumerator]; + Node *node; + while ((node = [enumerator nextObject]) != nil) { + NSPoint nodePos = [transformer toScreen:[node point]]; + if (NSPointInRect(nodePos, selectionBox)) { + [renderer setNode:node highlighted:YES]; + } else { + [renderer setNode:node highlighted:NO]; + } + } + } 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]; + } +} + +- (void) mouseReleaseAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + return; + + if (state == SelectBoxState) { + PickSupport *ps = [[self doc] pickSupport]; + Transformer *transformer = [renderer transformer]; + + if (!(mask & unionSelectMask)) { + [ps deselectAllNodes]; + [ps deselectAllEdges]; + } + + Graph *graph = [[self doc] graph]; + if (dragSelectMode & DragSelectsNodes) { + for (Node *node in [graph nodes]) { + NSPoint nodePos = [transformer toScreen:[node point]]; + if (NSPointInRect(nodePos, selectionBox)) { + [ps selectNode:node]; + } + } + } + if (dragSelectMode & DragSelectsEdges) { + for (Edge *edge in [graph edges]) { + NSPoint edgePos = [transformer toScreen:[edge mid]]; + if (NSPointInRect(edgePos, selectionBox)) { + [ps selectEdge:edge]; + } + } + } + + [self 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]; + } + + state = QuietState; +} + +- (void) mouseDoubleClickAt:(NSPoint)pos withButton:(MouseButton)button andMask:(InputMask)mask { + if (button != LeftButton) + 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) keyPressed:(unsigned int)keyVal withMask:(InputMask)mask { + if (keyVal == GDK_KEY_N && mask == ShiftMask) { + [self setDragSelectMode:DragSelectsNodes]; + } else if (keyVal == GDK_KEY_E && mask == ShiftMask) { + [self setDragSelectMode:DragSelectsEdges]; + } else if (keyVal == GDK_KEY_B && mask == ShiftMask) { + [self setDragSelectMode:DragSelectsBoth]; + } else if (keyVal == GDK_KEY_D && (!mask || mask == ShiftMask)) { + PickSupport *ps = [[self doc] pickSupport]; + for (Node* node in [ps selectedNodes]) { + NSRect b = [node boundingRect]; + NSLog(@"%@ @ (%f,%f) {style=%@, label=%@, data=%@, bounds=(%f,%f),(%fx%f)}", + [node name], + [node point].x, + [node point].y, + [[node style] name], + [node label], + [[node data] tikzList], + b.origin.x, b.origin.y, b.size.width, b.size.height); + } + for (Edge* edge in [ps selectedEdges]) { + NSRect b = [edge boundingRect]; + NSLog(@"%@:%@->%@:%@ {\n" + @" style=%@, data=%@,\n" + @" bend=%d, weight=%f, inAngle=%d, outAngle=%d, bendMode=%d,\n" + @" head=(%f,%f), headTan=(%f,%f) leftHeadNormal=(%f,%f), rightHeadNormal=(%f,%f),\n" + @" cp1=(%f,%f),\n" + @" mid=(%f,%f), midTan=(%f,%f), leftNormal=(%f,%f), rightNormal=(%f,%f)\n" + @" cp2=(%f,%f),\n" + @" tail=(%f,%f), tailTan=(%f,%f), leftTailNormal=(%f,%f), rightTailNormal=(%f,%f),\n" + @" isSelfLoop=%s, isStraight=%s,\n" + @" bounds=(%f,%f),(%fx%f)\n" + @"}", + [[edge source] name], + [edge sourceAnchor], + [[edge target] name], + [edge targetAnchor], + [[edge style] name], + [[edge data] tikzList], + [edge bend], + [edge weight], + [edge inAngle], + [edge outAngle], + [edge bendMode], + [edge head].x, + [edge head].y, + [edge headTan].x, + [edge headTan].y, + [edge leftHeadNormal].x, + [edge leftHeadNormal].y, + [edge rightHeadNormal].x, + [edge rightHeadNormal].y, + [edge cp1].x, + [edge cp1].y, + [edge mid].x, + [edge mid].y, + [edge midTan].x, + [edge midTan].y, + [edge leftNormal].x, + [edge leftNormal].y, + [edge rightNormal].x, + [edge rightNormal].y, + [edge cp2].x, + [edge cp2].y, + [edge tail].x, + [edge tail].y, + [edge tailTan].x, + [edge tailTan].y, + [edge leftTailNormal].x, + [edge leftTailNormal].y, + [edge rightTailNormal].x, + [edge rightTailNormal].y, + [edge isSelfLoop] ? "yes" : "no", + [edge isStraight] ? "yes" : "no", + b.origin.x, b.origin.y, b.size.width, b.size.height); + } + } +} + +- (void) renderWithContext:(id<RenderContext>)context onSurface:(id<Surface>)surface { + 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) loadConfiguration:(Configuration*)config { + NSString *mode = [config stringEntry:@"Drag select mode" + inGroup:@"SelectTool"]; + if ([mode isEqualToString:@"nodes"]) { + [self setDragSelectMode:DragSelectsNodes]; + } else if ([mode isEqualToString:@"edges"]) { + [self setDragSelectMode:DragSelectsEdges]; + } else if ([mode isEqualToString:@"both"]) { + [self setDragSelectMode:DragSelectsBoth]; + } +} + +- (void) saveConfiguration:(Configuration*)config { + switch (dragSelectMode) { + case DragSelectsNodes: + [config setStringEntry:@"Drag select mode" + inGroup:@"SelectTool" + value:@"nodes"]; + break; + case DragSelectsEdges: + [config setStringEntry:@"Drag select mode" + inGroup:@"SelectTool" + value:@"edges"]; + break; + case DragSelectsBoth: + [config setStringEntry:@"Drag select mode" + inGroup:@"SelectTool" + value:@"both"]; + break; + } +} + +@end + +@implementation SelectTool (Private) +- (TikzDocument*) doc { + return [renderer document]; +} + +- (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; + [node setPoint:[transformer fromScreen:p]]; + } +} + +- (void) deselectAllNodes { + [[[self doc] pickSupport] deselectAllNodes]; +} + +- (void) deselectAllEdges { + [[[self doc] pickSupport] deselectAllEdges]; +} + +- (void) deselectAll { + [[[self doc] pickSupport] deselectAllNodes]; + [[[self doc] pickSupport] deselectAllEdges]; +} + +- (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) setSelectionBox:(NSRect)box { + NSRect invRect = NSUnionRect (selectionBox, box); + selectionBox = box; + [renderer invalidateRect:NSInsetRect (invRect, -2, -2)]; +} + +- (void) clearSelectionBox { + NSRect oldRect = selectionBox; + + selectionBox = NSZeroRect; + + [renderer invalidateRect:NSInsetRect (oldRect, -2, -2)]; + [renderer clearHighlightedNodes]; +} + +- (BOOL) selectionBoxContainsNode:(Node*)node { + if (!NSIsEmptyRect (selectionBox)) + return NO; + + Transformer *transf = [[renderer surface] transformer]; + NSPoint screenPt = [transf toScreen:[node point]]; + return NSPointInRect(screenPt, selectionBox); +} +@end + +static void drag_select_mode_cb (GtkToggleButton *button, SelectTool *tool) { + if (gtk_toggle_button_get_active (button)) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + DragSelectMode buttonMode = + (DragSelectMode) g_object_get_data ( + G_OBJECT (button), + DRAG_SELECT_MODE_KEY); + [tool setDragSelectMode:buttonMode]; + [pool drain]; + } +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/SelectionPane.h b/tikzit-1/src/gtk/SelectionPane.h new file mode 100644 index 0000000..57a766a --- /dev/null +++ b/tikzit-1/src/gtk/SelectionPane.h @@ -0,0 +1,56 @@ +/* + * Copyright 2011-2012 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 Configuration; +@class EdgeStylesModel; +@class NodeStylesModel; +@class StyleManager; +@class TikzDocument; + +@interface SelectionPane: NSObject { + TikzDocument *document; + + NodeStylesModel *nodeStylesModel; + EdgeStylesModel *edgeStylesModel; + + GtkWidget *layout; + + GtkWidget *nodeStyleCombo; + GtkWidget *applyNodeStyleButton; + GtkWidget *clearNodeStyleButton; + GtkWidget *edgeStyleCombo; + GtkWidget *applyEdgeStyleButton; + GtkWidget *clearEdgeStyleButton; +} + +@property (retain) TikzDocument *document; +@property (assign) BOOL visible; +@property (readonly) GtkWidget *gtkWidget; + +- (id) initWithStyleManager:(StyleManager*)mgr; +- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm + andEdgeStylesModel:(EdgeStylesModel*)esm; + +- (void) loadConfiguration:(Configuration*)config; +- (void) saveConfiguration:(Configuration*)config; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/SelectionPane.m b/tikzit-1/src/gtk/SelectionPane.m new file mode 100644 index 0000000..2931258 --- /dev/null +++ b/tikzit-1/src/gtk/SelectionPane.m @@ -0,0 +1,432 @@ +/* + * Copyright 2011-2012 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 "SelectionPane.h" + +#import "Configuration.h" +#import "EdgeStylesModel.h" +#import "NodeStylesModel.h" +#import "TikzDocument.h" + +#import "gtkhelpers.h" + +// {{{ Internal interfaces + +static void node_style_changed_cb (GtkComboBox *widget, SelectionPane *pane); +static void apply_node_style_button_cb (GtkButton *widget, SelectionPane *pane); +static void clear_node_style_button_cb (GtkButton *widget, SelectionPane *pane); +static void edge_style_changed_cb (GtkComboBox *widget, SelectionPane *pane); +static void apply_edge_style_button_cb (GtkButton *widget, SelectionPane *pane); +static void clear_edge_style_button_cb (GtkButton *widget, SelectionPane *pane); + +static void setup_style_cell_layout (GtkCellLayout *cell_layout, gint pixbuf_col, gint name_col); + +@interface SelectionPane (Notifications) +- (void) nodeSelectionChanged:(NSNotification*)n; +- (void) edgeSelectionChanged:(NSNotification*)n; +@end + +@interface SelectionPane (Private) +- (void) _updateNodeStyleButtons; +- (void) _updateEdgeStyleButtons; +- (NodeStyle*) _selectedNodeStyle; +- (EdgeStyle*) _selectedEdgeStyle; +- (void) _applyNodeStyle; +- (void) _clearNodeStyle; +- (void) _applyEdgeStyle; +- (void) _clearEdgeStyle; +@end + +// }}} +// {{{ API + +@implementation SelectionPane + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithStyleManager:(StyleManager*)sm { + return [self initWithNodeStylesModel:[NodeStylesModel modelWithStyleManager:sm] + andEdgeStylesModel:[EdgeStylesModel modelWithStyleManager:sm]]; +} + +- (id) initWithNodeStylesModel:(NodeStylesModel*)nsm + andEdgeStylesModel:(EdgeStylesModel*)esm { + self = [super init]; + + if (self) { + nodeStylesModel = [nsm retain]; + edgeStylesModel = [esm retain]; + + layout = gtk_vbox_new (FALSE, 0); + g_object_ref_sink (layout); + gtk_widget_show (layout); + + GtkWidget *label = gtk_label_new ("Selection"); + label_set_bold (GTK_LABEL (label)); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (layout), label, + FALSE, FALSE, 0); + + GtkWidget *lvl1_box = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (layout), lvl1_box, + FALSE, FALSE, 3); + + nodeStyleCombo = gtk_combo_box_new_with_model ([nodeStylesModel model]); + g_object_ref_sink (nodeStyleCombo); + setup_style_cell_layout (GTK_CELL_LAYOUT (nodeStyleCombo), + NODE_STYLES_ICON_COL, + NODE_STYLES_NAME_COL); + g_signal_connect (G_OBJECT (nodeStyleCombo), + "changed", + G_CALLBACK (node_style_changed_cb), + self); + gtk_box_pack_start (GTK_BOX (lvl1_box), nodeStyleCombo, + FALSE, FALSE, 0); + + GtkWidget *lvl2_box = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (lvl1_box), lvl2_box, + FALSE, FALSE, 0); + + applyNodeStyleButton = gtk_button_new_with_label ("Apply"); + g_object_ref_sink (applyNodeStyleButton); + gtk_widget_set_tooltip_text (applyNodeStyleButton, "Apply style to selected nodes"); + gtk_widget_set_sensitive (applyNodeStyleButton, FALSE); + g_signal_connect (G_OBJECT (applyNodeStyleButton), + "clicked", + G_CALLBACK (apply_node_style_button_cb), + self); + gtk_box_pack_start (GTK_BOX (lvl2_box), applyNodeStyleButton, + FALSE, FALSE, 0); + + clearNodeStyleButton = gtk_button_new_with_label ("Clear"); + g_object_ref_sink (clearNodeStyleButton); + gtk_widget_set_tooltip_text (clearNodeStyleButton, "Clear style from selected nodes"); + gtk_widget_set_sensitive (clearNodeStyleButton, FALSE); + g_signal_connect (G_OBJECT (clearNodeStyleButton), + "clicked", + G_CALLBACK (clear_node_style_button_cb), + self); + gtk_box_pack_start (GTK_BOX (lvl2_box), clearNodeStyleButton, + FALSE, FALSE, 0); + + lvl1_box = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (layout), lvl1_box, + FALSE, FALSE, 3); + + edgeStyleCombo = gtk_combo_box_new_with_model ([edgeStylesModel model]); + g_object_ref_sink (edgeStyleCombo); + setup_style_cell_layout (GTK_CELL_LAYOUT (edgeStyleCombo), + EDGE_STYLES_ICON_COL, + EDGE_STYLES_NAME_COL); + g_signal_connect (G_OBJECT (edgeStyleCombo), + "changed", + G_CALLBACK (edge_style_changed_cb), + self); + gtk_box_pack_start (GTK_BOX (lvl1_box), edgeStyleCombo, + FALSE, FALSE, 0); + + lvl2_box = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (lvl1_box), lvl2_box, + FALSE, FALSE, 0); + + applyEdgeStyleButton = gtk_button_new_with_label ("Apply"); + g_object_ref_sink (applyEdgeStyleButton); + gtk_widget_set_tooltip_text (applyEdgeStyleButton, "Apply style to selected edges"); + gtk_widget_set_sensitive (applyEdgeStyleButton, FALSE); + g_signal_connect (G_OBJECT (applyEdgeStyleButton), + "clicked", + G_CALLBACK (apply_edge_style_button_cb), + self); + gtk_box_pack_start (GTK_BOX (lvl2_box), applyEdgeStyleButton, + FALSE, FALSE, 0); + + clearEdgeStyleButton = gtk_button_new_with_label ("Clear"); + g_object_ref_sink (clearEdgeStyleButton); + gtk_widget_set_tooltip_text (clearEdgeStyleButton, "Clear style from selected edges"); + gtk_widget_set_sensitive (clearEdgeStyleButton, FALSE); + g_signal_connect (G_OBJECT (clearEdgeStyleButton), + "clicked", + G_CALLBACK (clear_edge_style_button_cb), + self); + gtk_box_pack_start (GTK_BOX (lvl2_box), clearEdgeStyleButton, + FALSE, FALSE, 0); + + gtk_widget_show_all (layout); + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + + g_object_unref (nodeStyleCombo); + g_object_unref (applyNodeStyleButton); + g_object_unref (clearNodeStyleButton); + g_object_unref (edgeStyleCombo); + g_object_unref (applyEdgeStyleButton); + g_object_unref (clearEdgeStyleButton); + + g_object_unref (layout); + + [nodeStylesModel release]; + [edgeStylesModel release]; + + [document release]; + + [super dealloc]; +} + +- (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 (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]]; + } + + [self _updateNodeStyleButtons]; + [self _updateEdgeStyleButtons]; +} + +- (BOOL) visible { + return gtk_widget_get_visible (layout); +} + +- (void) setVisible:(BOOL)visible { + gtk_widget_set_visible (layout, visible); +} + +- (GtkWidget*) gtkWidget { + return layout; +} + +- (void) loadConfiguration:(Configuration*)config { + NSString *nodeStyleName = [config stringEntry:@"SelectedNodeStyle" + inGroup:@"SelectionPane" + withDefault:nil]; + NodeStyle *nodeStyle = [[nodeStylesModel styleManager] nodeStyleForName:nodeStyleName]; + if (nodeStyle == nil) { + gtk_combo_box_set_active (GTK_COMBO_BOX (nodeStyleCombo), -1); + } else { + GtkTreeIter *iter = [nodeStylesModel iterFromStyle:nodeStyle]; + if (iter) { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (nodeStyleCombo), iter); + gtk_tree_iter_free (iter); + } + } + + NSString *edgeStyleName = [config stringEntry:@"SelectedEdgeStyle" + inGroup:@"SelectionPane" + withDefault:nil]; + EdgeStyle *edgeStyle = [[edgeStylesModel styleManager] edgeStyleForName:edgeStyleName]; + if (edgeStyle == nil) { + gtk_combo_box_set_active (GTK_COMBO_BOX (edgeStyleCombo), -1); + } else { + GtkTreeIter *iter = [edgeStylesModel iterFromStyle:edgeStyle]; + if (iter) { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (edgeStyleCombo), iter); + gtk_tree_iter_free (iter); + } + } +} + +- (void) saveConfiguration:(Configuration*)config { + [config setStringEntry:@"SelectedNodeStyle" + inGroup:@"SelectionPane" + value:[[self _selectedNodeStyle] name]]; + [config setStringEntry:@"SelectedEdgeStyle" + inGroup:@"SelectionPane" + value:[[self _selectedEdgeStyle] name]]; +} + +@end + +// }}} +// {{{ Notifications + +@implementation SelectionPane (Notifications) +- (void) nodeSelectionChanged:(NSNotification*)n { + [self _updateNodeStyleButtons]; +} + +- (void) edgeSelectionChanged:(NSNotification*)n { + [self _updateEdgeStyleButtons]; +} +@end + +// }}} +// {{{ Private + +@implementation SelectionPane (Private) +- (void) _updateNodeStyleButtons { + gboolean hasNodeSelection = [[[document pickSupport] selectedNodes] count] > 0; + + gtk_widget_set_sensitive (applyNodeStyleButton, + hasNodeSelection && [self _selectedNodeStyle] != nil); + gtk_widget_set_sensitive (clearNodeStyleButton, hasNodeSelection); +} + +- (void) _updateEdgeStyleButtons { + gboolean hasEdgeSelection = [[[document pickSupport] selectedEdges] count] > 0; + + gtk_widget_set_sensitive (applyEdgeStyleButton, + hasEdgeSelection && [self _selectedEdgeStyle] != nil); + gtk_widget_set_sensitive (clearEdgeStyleButton, hasEdgeSelection); +} + +- (NodeStyle*) _selectedNodeStyle { + GtkTreeIter iter; + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (nodeStyleCombo), &iter)) { + return [nodeStylesModel styleFromIter:&iter]; + } else { + return nil; + } +} + +- (EdgeStyle*) _selectedEdgeStyle { + GtkTreeIter iter; + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (edgeStyleCombo), &iter)) { + return [edgeStylesModel styleFromIter:&iter]; + } else { + return nil; + } +} + +- (void) _applyNodeStyle { + [document startModifyNodes:[[document pickSupport] selectedNodes]]; + + NodeStyle *style = [self _selectedNodeStyle]; + for (Node *node in [[document pickSupport] selectedNodes]) { + [node setStyle:style]; + } + + [document endModifyNodes]; +} + +- (void) _clearNodeStyle { + [document startModifyNodes:[[document pickSupport] selectedNodes]]; + + for (Node *node in [[document pickSupport] selectedNodes]) { + [node setStyle:nil]; + } + + [document endModifyNodes]; +} + +- (void) _applyEdgeStyle { + [document startModifyEdges:[[document pickSupport] selectedEdges]]; + + EdgeStyle *style = [self _selectedEdgeStyle]; + for (Edge *edge in [[document pickSupport] selectedEdges]) { + [edge setStyle:style]; + } + + [document endModifyEdges]; +} + +- (void) _clearEdgeStyle { + [document startModifyEdges:[[document pickSupport] selectedEdges]]; + + for (Edge *edge in [[document pickSupport] selectedEdges]) { + [edge setStyle:nil]; + } + + [document endModifyEdges]; +} +@end + +// }}} +// {{{ GTK+ callbacks + +static void node_style_changed_cb (GtkComboBox *widget, SelectionPane *pane) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pane _updateNodeStyleButtons]; + [pool drain]; +} + +static void apply_node_style_button_cb (GtkButton *widget, SelectionPane *pane) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pane _applyNodeStyle]; + [pool drain]; +} + +static void clear_node_style_button_cb (GtkButton *widget, SelectionPane *pane) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pane _clearNodeStyle]; + [pool drain]; +} + +static void edge_style_changed_cb (GtkComboBox *widget, SelectionPane *pane) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pane _updateEdgeStyleButtons]; + [pool drain]; +} + +static void apply_edge_style_button_cb (GtkButton *widget, SelectionPane *pane) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pane _applyEdgeStyle]; + [pool drain]; +} + +static void clear_edge_style_button_cb (GtkButton *widget, SelectionPane *pane) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [pane _clearEdgeStyle]; + [pool drain]; +} + +// }}} +// +static void setup_style_cell_layout (GtkCellLayout *cell_layout, gint pixbuf_col, gint name_col) { + gtk_cell_layout_clear (cell_layout); + GtkCellRenderer *pixbuf_renderer = gtk_cell_renderer_pixbuf_new (); + gtk_cell_layout_pack_start (cell_layout, pixbuf_renderer, FALSE); + gtk_cell_layout_set_attributes ( + cell_layout, + pixbuf_renderer, + "pixbuf", pixbuf_col, + NULL); + GtkCellRenderer *text_renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (cell_layout, text_renderer, FALSE); + gtk_cell_layout_set_attributes ( + cell_layout, + text_renderer, + "text", name_col, + NULL); +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/SettingsDialog.h b/tikzit-1/src/gtk/SettingsDialog.h new file mode 100644 index 0000000..0f687b3 --- /dev/null +++ b/tikzit-1/src/gtk/SettingsDialog.h @@ -0,0 +1,54 @@ +/* + * Copyright 2012 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 Configuration; +@class EdgeStylesPalette; +@class NodeStylesPalette; +@class StyleManager; + +@interface SettingsDialog: NSObject { + Configuration *configuration; + StyleManager *styleManager; + StyleManager *tempStyleManager; + NodeStylesPalette *nodePalette; + EdgeStylesPalette *edgePalette; + + GtkWindow *parentWindow; + GtkWindow *window; + + // we don't keep any refs, as we control + // the top window + GtkEntry *pdflatexPathEntry; +} + +@property (retain) Configuration *configuration; +@property (retain) StyleManager *styleManager; +@property (assign) GtkWindow *parentWindow; +@property (assign,getter=isVisible) BOOL visible; + +- (id) initWithConfiguration:(Configuration*)c andStyleManager:(StyleManager*)m; + +- (void) present; +- (void) show; +- (void) hide; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/SettingsDialog.m b/tikzit-1/src/gtk/SettingsDialog.m new file mode 100644 index 0000000..bdb5db6 --- /dev/null +++ b/tikzit-1/src/gtk/SettingsDialog.m @@ -0,0 +1,328 @@ +/* + * Copyright 2012 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 "SettingsDialog.h" + +#import "Application.h" +#import "Configuration.h" +#import "EdgeStylesPalette.h" +#import "NodeStylesPalette.h" +#import "StyleManager.h" + +// {{{ Internal interfaces +// {{{ Signals +static gboolean window_delete_event_cb (GtkWidget *widget, + GdkEvent *event, + SettingsDialog *dialog); +static void ok_button_clicked_cb (GtkButton *widget, SettingsDialog *dialog); +static void cancel_button_clicked_cb (GtkButton *widget, SettingsDialog *dialog); +// }}} + +@interface SettingsDialog (Private) +- (void) loadUi; +- (void) save; +- (void) revert; +@end + +// }}} +// {{{ API + +@implementation SettingsDialog + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithConfiguration:(Configuration*)c andStyleManager:(StyleManager*)m { + self = [super init]; + + if (self) { + configuration = [c retain]; + styleManager = [m retain]; + tempStyleManager = [m copy]; + } + + return self; +} + +- (void) dealloc { + if (window) { + gtk_widget_destroy (GTK_WIDGET (window)); + } + if (parentWindow) { + g_object_unref (parentWindow); + } + + [configuration release]; + [tempStyleManager release]; + [styleManager release]; + [nodePalette release]; + [edgePalette release]; + + [super dealloc]; +} + +- (Configuration*) configuration { + return configuration; +} + +- (void) setConfiguration:(Configuration*)c { + [c retain]; + [configuration release]; + configuration = c; + [self revert]; +} + +- (StyleManager*) styleManager { + return styleManager; +} + +- (void) setStyleManager:(StyleManager*)m { + [m retain]; + [styleManager release]; + styleManager = m; +} + +- (GtkWindow*) parentWindow { + return parentWindow; +} + +- (void) setParentWindow:(GtkWindow*)parent { + GtkWindow *oldParent = parentWindow; + + if (parent) + g_object_ref (parent); + parentWindow = parent; + if (oldParent) + g_object_unref (oldParent); + + if (window) { + gtk_window_set_transient_for (window, parentWindow); + } +} + +- (void) present { + [self loadUi]; + [self revert]; + gtk_window_present (GTK_WINDOW (window)); +} + +- (void) show { + [self loadUi]; + [self revert]; + gtk_widget_show (GTK_WIDGET (window)); +} + +- (void) hide { + if (!window) { + return; + } + 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]; + } +} + +@end + +// }}} +// {{{ Private + +@implementation SettingsDialog (Private) +- (void) loadUi { + if (window) { + return; + } + + nodePalette = [[NodeStylesPalette alloc] initWithManager:tempStyleManager]; + edgePalette = [[EdgeStylesPalette alloc] initWithManager:tempStyleManager]; + + window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); + gtk_window_set_default_size (window, 570, -1); + gtk_window_set_title (window, "TikZiT Configuration"); + gtk_window_set_modal (window, TRUE); + gtk_window_set_position (window, GTK_WIN_POS_CENTER_ON_PARENT); + 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, 18); + gtk_container_set_border_width (GTK_CONTAINER (mainBox), 12); + gtk_container_add (GTK_CONTAINER (window), mainBox); + gtk_widget_show (mainBox); + +#ifdef HAVE_POPPLER + /* + * Path for pdflatex + */ + + GtkWidget *pdflatexFrame = gtk_frame_new ("Previews"); + gtk_box_pack_start (GTK_BOX (mainBox), pdflatexFrame, TRUE, TRUE, 0); + + GtkBox *pdflatexBox = GTK_BOX (gtk_hbox_new (FALSE, 6)); + gtk_container_add (GTK_CONTAINER (pdflatexFrame), GTK_WIDGET (pdflatexBox)); + gtk_container_set_border_width (GTK_CONTAINER (pdflatexBox), 6); + + GtkWidget *pdflatexLabel = gtk_label_new ("Path to pdflatex:"); + gtk_misc_set_alignment (GTK_MISC (pdflatexLabel), 0, 0.5); + gtk_box_pack_start (pdflatexBox, + pdflatexLabel, + FALSE, TRUE, 0); + + pdflatexPathEntry = GTK_ENTRY (gtk_entry_new ()); + gtk_box_pack_start (pdflatexBox, + GTK_WIDGET (pdflatexPathEntry), + TRUE, TRUE, 0); + + gtk_widget_show_all (pdflatexFrame); +#else + pdflatexPathEntry = NULL; +#endif + + /* + * Node styles + */ + GtkWidget *nodeStylesFrame = gtk_frame_new ("Node Styles"); + gtk_widget_show (nodeStylesFrame); + gtk_box_pack_start (GTK_BOX (mainBox), nodeStylesFrame, TRUE, TRUE, 0); + gtk_container_add (GTK_CONTAINER (nodeStylesFrame), + GTK_WIDGET ([nodePalette widget])); + gtk_widget_show ([nodePalette widget]); + + + /* + * Edge styles + */ + GtkWidget *edgeStylesFrame = gtk_frame_new ("Edge Styles"); + gtk_widget_show (edgeStylesFrame); + gtk_box_pack_start (GTK_BOX (mainBox), edgeStylesFrame, TRUE, TRUE, 0); + gtk_container_add (GTK_CONTAINER (edgeStylesFrame), + GTK_WIDGET ([edgePalette widget])); + gtk_widget_show ([edgePalette widget]); + + + /* + * Bottom buttons + */ + + GtkContainer *buttonBox = GTK_CONTAINER (gtk_hbutton_box_new ()); + gtk_box_set_spacing (GTK_BOX (buttonBox), 6); + 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 *okButton = gtk_button_new_from_stock (GTK_STOCK_OK); + gtk_container_add (buttonBox, okButton); + g_signal_connect (okButton, + "clicked", + G_CALLBACK (ok_button_clicked_cb), + self); + + GtkWidget *cancelButton = gtk_button_new_from_stock (GTK_STOCK_CANCEL); + gtk_container_add (buttonBox, cancelButton); + g_signal_connect (cancelButton, + "clicked", + G_CALLBACK (cancel_button_clicked_cb), + self); + + gtk_widget_show_all (GTK_WIDGET (buttonBox)); + + [self revert]; +} + +- (void) save { + if (!window) + return; + +#ifdef HAVE_POPPLER + const gchar *path = gtk_entry_get_text (pdflatexPathEntry); + if (path && *path) { + [configuration setStringEntry:@"pdflatex" + inGroup:@"Previews" + value:[NSString stringWithUTF8String:path]]; + } +#endif + + [styleManager updateFromManager:tempStyleManager]; + + [app saveConfiguration]; +} + +- (void) revert { + if (!window) + return; + +#ifdef HAVE_POPPLER + NSString *path = [configuration stringEntry:@"pdflatex" + inGroup:@"Previews" + withDefault:@"pdflatex"]; + gtk_entry_set_text (pdflatexPathEntry, [path UTF8String]); +#endif + + [tempStyleManager updateFromManager:styleManager]; +} +@end + +// }}} +// {{{ GTK+ callbacks + +static gboolean window_delete_event_cb (GtkWidget *widget, + GdkEvent *event, + SettingsDialog *dialog) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [dialog hide]; + [pool drain]; + return TRUE; // we dealt with this event +} + +static void ok_button_clicked_cb (GtkButton *widget, SettingsDialog *dialog) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [dialog save]; + [dialog hide]; + [pool drain]; +} + +static void cancel_button_clicked_cb (GtkButton *widget, SettingsDialog *dialog) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [dialog hide]; + [pool drain]; +} + +// }}} + +// vim:ft=objc:ts=4:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/Shape+Render.h b/tikzit-1/src/gtk/Shape+Render.h new file mode 100644 index 0000000..a744c77 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/Shape+Render.m b/tikzit-1/src/gtk/Shape+Render.m new file mode 100644 index 0000000..924bb24 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/StyleManager+Storage.h b/tikzit-1/src/gtk/StyleManager+Storage.h new file mode 100644 index 0000000..1727786 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/StyleManager+Storage.m b/tikzit-1/src/gtk/StyleManager+Storage.m new file mode 100644 index 0000000..f4c8232 --- /dev/null +++ b/tikzit-1/src/gtk/StyleManager+Storage.m @@ -0,0 +1,82 @@ +/* + * 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]) { + NSLog(@"No styles config found"); + 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-1/src/gtk/Surface.h b/tikzit-1/src/gtk/Surface.h new file mode 100644 index 0000000..db4288e --- /dev/null +++ b/tikzit-1/src/gtk/Surface.h @@ -0,0 +1,107 @@ +/* + * 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" + +typedef enum { + NormalCursor, + ResizeRightCursor, + ResizeBottomRightCursor, + ResizeBottomCursor, + ResizeBottomLeftCursor, + ResizeLeftCursor, + ResizeTopLeftCursor, + ResizeTopCursor, + ResizeTopRightCursor +} Cursor; + +@protocol Surface; + +@protocol RenderDelegate <NSObject> +- (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 <NSObject> + +/** + * 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; + +- (void) setCursor:(Cursor)c; + +- (BOOL) hasFocus; +- (void) renderFocus; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/TZFoundation.h b/tikzit-1/src/gtk/TZFoundation.h new file mode 100644 index 0000000..2ff20ca --- /dev/null +++ b/tikzit-1/src/gtk/TZFoundation.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 <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" +#import "NSString+Tikz.h" +#import "NSString+Util.h" + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/TikzDocument.h b/tikzit-1/src/gtk/TikzDocument.h new file mode 100644 index 0000000..5d15d13 --- /dev/null +++ b/tikzit-1/src/gtk/TikzDocument.h @@ -0,0 +1,149 @@ +// +// 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; + NSMapTable *nodesetBeingModifiedOldCopy; + NSSet *edgesetBeingModified; + NSMapTable *edgesetBeingModifiedOldCopy; + 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 error:(NSError**)error; ++ (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 error:(NSError**)error; +- (id) initFromFile:(NSString*)path styleManager:(StyleManager*)manager error:(NSError**)error; + +@property (readonly) Graph *graph; +@property (readonly) PickSupport *pickSupport; +@property (readonly) NSString *path; +@property (readonly) NSString *name; +@property (readonly) NSString *suggestedFileName; +@property (readonly) BOOL hasUnsavedChanges; +@property (retain) StyleManager *styleManager; +@property (readonly) NSString *tikz; +@property (readonly) BOOL canUndo; +@property (readonly) BOOL canRedo; +@property (readonly) NSString *undoName; +@property (readonly) NSString *redoName; + +- (BOOL) updateTikz:(NSString*)t error:(NSError**)error; + +- (Graph*) selectionCut; +- (Graph*) selectionCopy; +- (void) paste:(Graph*)graph; +- (void) pasteFromTikz:(NSString*)tikz; + +// some convenience methods: +- (BOOL) isNodeSelected:(Node*)node; +- (BOOL) isEdgeSelected:(Edge*)edge; +- (NSEnumerator*) nodeEnumerator; +- (NSEnumerator*) edgeEnumerator; + +- (void) undo; +- (void) redo; + +- (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) startModifyEdges:(NSSet*)edges; +- (void) modifyEdgesCheckPoint; +- (void) endModifyEdges; +- (void) cancelModifyEdges; + +- (void) startChangeBoundingBox; +- (void) changeBoundingBoxCheckPoint; +- (void) endChangeBoundingBox; +- (void) cancelChangeBoundingBox; + +- (void) startChangeGraphProperties; +- (void) changeGraphPropertiesCheckPoint; +- (void) endChangeGraphProperties; +- (void) cancelChangeGraphProperties; + +- (void) removeSelected; +- (void) addNode:(Node*)node; +- (void) removeNode:(Node*)node; +- (void) addEdge:(Edge*)edge; +- (void) removeEdge:(Edge*)edge; +- (void) shiftSelectedNodesByPoint:(NSPoint)offset; +- (void) insertGraph:(Graph*)g; +- (void) flipSelectedNodesHorizontally; +- (void) flipSelectedNodesVertically; +- (void) reverseSelectedEdges; +- (void) bringSelectionForward; +- (void) bringSelectionToFront; +- (void) sendSelectionBackward; +- (void) sendSelectionToBack; + +- (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-1/src/gtk/TikzDocument.m b/tikzit-1/src/gtk/TikzDocument.m new file mode 100644 index 0000000..bff5a2e --- /dev/null +++ b/tikzit-1/src/gtk/TikzDocument.m @@ -0,0 +1,911 @@ +// +// 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" + +@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 + error:(NSError**)error +{ + return [[[TikzDocument alloc] initWithTikz:t + styleManager:manager + error:error] 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 + error:(NSError**)error +{ + self = [self initWithStyleManager:manager]; + + if (self) { + [undoManager disableUndoRegistration]; + BOOL success = [self updateTikz:t error:error]; + if (!success) { + [self release]; + return nil; + } + [undoManager enableUndoRegistration]; + hasChanges = NO; + } + + return self; +} + +- (id) initFromFile:(NSString*)pth + styleManager:(StyleManager*)manager + error:(NSError**)error +{ + NSString *t = [NSString stringWithContentsOfFile:pth error:error]; + if (t == nil) { + [self release]; + return nil; + } + + self = [self initWithTikz:t styleManager:manager error:error]; + + 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]; +} + +@synthesize graph, pickSupport, 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) postUndoStackChanged { + [[NSNotificationCenter defaultCenter] postNotificationName:@"UndoStackChanged" object:self]; +} + +- (NSString*) tikz { + return tikz; +} + +- (BOOL) updateTikz:(NSString*)t error:(NSError**)error { + if (t == nil) { + t = [NSString string]; + } + if (t == tikz || [t isEqual:tikz]) { + return YES; + } + + Graph *g = [Graph graphFromTikz:t error:error]; + if (g) { + // updateTikz actually generates a graph from the tikz, + // and generates the final tikz from that + [self startUndoGroup]; + [self setGraph:g]; + [self nameAndEndUndoGroup:@"Update tikz"]; + return YES; + } + + return NO; +} + +- (Graph*) selectionCut { + Graph *selection = [self selectionCopy]; + [self startUndoGroup]; + [self removeSelected]; + [self nameAndEndUndoGroup:@"Cut"]; + return selection; +} + +- (Graph*) selectionCopy { + 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:[NSSet setWithArray:[g nodes]] replacingSelection:YES]; +} + +- (void) pasteFromTikz:(NSString*)t { + Graph *clipboard = [Graph graphFromTikz:t]; + if (clipboard) { + [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) startModifyEdges:(NSSet*)edges { + if (edgesetBeingModified != nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Already modifying an edge set"]; + } + + edgesetBeingModified = [edges copy]; + edgesetBeingModifiedOldCopy = [[Graph edgeTableForEdges:edges] retain]; +} + +- (void) modifyEdgesCheckPoint { + [self regenerateTikz]; + GraphChange *change = [GraphChange propertyChangeOfEdgesFromOldCopies:edgesetBeingModifiedOldCopy + toNewCopies:[Graph edgeTableForEdges:edgesetBeingModified]]; + [self postIncompleteGraphChange:change]; +} + +- (void) _finishModifyEdgesCancelled:(BOOL)cancelled { + if (edgesetBeingModified == nil) { + [NSException raise:@"NSInternalInconsistencyException" format:@"Not modifying an edge"]; + } + + GraphChange *change = [GraphChange propertyChangeOfEdgesFromOldCopies:edgesetBeingModifiedOldCopy + toNewCopies:[Graph edgeTableForEdges:edgesetBeingModified]]; + [self _finishModifySequence:change withName:@"Modify edges" cancelled:cancelled]; + + [edgesetBeingModified release]; + edgesetBeingModified = nil; + [edgesetBeingModifiedOldCopy release]; + edgesetBeingModifiedOldCopy = nil; +} + +- (void) endModifyEdges { [self _finishModifyEdgesCancelled:NO]; } +- (void) cancelModifyEdges { [self _finishModifyEdgesCancelled: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"]; +} + +- (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"]; +} + +- (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"]; + } +} + +- (void) reverseSelectedEdges { + if ([[pickSupport selectedEdges] count] > 0) { + GraphChange *change = [graph reverseEdges:[pickSupport selectedEdges]]; + [self completedGraphChange:change withName:@"Reverse edges"]; + } +} + +- (void) bringSelectionForward { + BOOL hasNodeSelection = [[pickSupport selectedNodes] count] > 0; + BOOL hasEdgeSelection = [[pickSupport selectedEdges] count] > 0; + if (!hasNodeSelection && !hasEdgeSelection) + return; + + [self startUndoGroup]; + GraphChange *nodeChange; + GraphChange *edgeChange; + if (hasNodeSelection) { + nodeChange = [graph bringNodesForward:[pickSupport selectedNodes]]; + [self registerUndoForChange:nodeChange]; + } + if (hasEdgeSelection) { + edgeChange = [graph bringEdgesForward:[pickSupport selectedEdges]]; + [self registerUndoForChange:edgeChange]; + } + [self nameAndEndUndoGroup:@"Bring forward"]; + [self regenerateTikz]; + if (hasNodeSelection) + [self postGraphChange:nodeChange]; + if (hasEdgeSelection) + [self postGraphChange:edgeChange]; +} + +- (void) bringSelectionToFront { + BOOL hasNodeSelection = [[pickSupport selectedNodes] count] > 0; + BOOL hasEdgeSelection = [[pickSupport selectedEdges] count] > 0; + if (!hasNodeSelection && !hasEdgeSelection) + return; + + [self startUndoGroup]; + GraphChange *nodeChange; + GraphChange *edgeChange; + if (hasNodeSelection) { + nodeChange = [graph bringNodesToFront:[pickSupport selectedNodes]]; + [self registerUndoForChange:nodeChange]; + } + if (hasEdgeSelection) { + edgeChange = [graph bringEdgesToFront:[pickSupport selectedEdges]]; + [self registerUndoForChange:edgeChange]; + } + [self nameAndEndUndoGroup:@"Bring to front"]; + [self regenerateTikz]; + if (hasNodeSelection) + [self postGraphChange:nodeChange]; + if (hasEdgeSelection) + [self postGraphChange:edgeChange]; +} + +- (void) sendSelectionBackward { + BOOL hasNodeSelection = [[pickSupport selectedNodes] count] > 0; + BOOL hasEdgeSelection = [[pickSupport selectedEdges] count] > 0; + if (!hasNodeSelection && !hasEdgeSelection) + return; + + [self startUndoGroup]; + GraphChange *nodeChange; + GraphChange *edgeChange; + if (hasNodeSelection) { + nodeChange = [graph sendNodesBackward:[pickSupport selectedNodes]]; + [self registerUndoForChange:nodeChange]; + } + if (hasEdgeSelection) { + edgeChange = [graph sendNodesBackward:[pickSupport selectedEdges]]; + [self registerUndoForChange:edgeChange]; + } + [self nameAndEndUndoGroup:@"Send backward"]; + [self regenerateTikz]; + if (hasNodeSelection) + [self postGraphChange:nodeChange]; + if (hasEdgeSelection) + [self postGraphChange:edgeChange]; +} + +- (void) sendSelectionToBack { + BOOL hasNodeSelection = [[pickSupport selectedNodes] count] > 0; + BOOL hasEdgeSelection = [[pickSupport selectedEdges] count] > 0; + if (!hasNodeSelection && !hasEdgeSelection) + return; + + [self startUndoGroup]; + GraphChange *nodeChange; + GraphChange *edgeChange; + if (hasNodeSelection) { + nodeChange = [graph sendNodesToBack:[pickSupport selectedNodes]]; + [self registerUndoForChange:nodeChange]; + } + if (hasEdgeSelection) { + edgeChange = [graph sendNodesToBack:[pickSupport selectedEdges]]; + [self registerUndoForChange:edgeChange]; + } + [self nameAndEndUndoGroup:@"Send to back"]; + [self regenerateTikz]; + if (hasNodeSelection) + [self postGraphChange:nodeChange]; + if (hasEdgeSelection) + [self postGraphChange:edgeChange]; +} + +- (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 { + if (change == nil) { + NSLog(@"No graph change given for change %@", name); + return; + } + [self registerUndoGroupForChange:change withName:name]; + [self regenerateTikz]; + [self postGraphChange:change]; +} + +- (void) attachStylesToGraph:(Graph*)g { + for (Node *n in [g nodes]) { + [n attachStyleFromTable:[styleManager nodeStyles]]; + } + for (Edge *e in [g edges]) { + [e attachStyleFromTable:[styleManager edgeStyles]]; + } +} + +- (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-1/src/gtk/Tool.h b/tikzit-1/src/gtk/Tool.h new file mode 100644 index 0000000..22c983e --- /dev/null +++ b/tikzit-1/src/gtk/Tool.h @@ -0,0 +1,41 @@ +/* + * 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 "TZFoundation.h" +#import "InputDelegate.h" +#import "Surface.h" + +#import <gtk/gtk.h> +#import <gdk-pixbuf/gdk-pixdata.h> + +@class Configuration; +@class GraphRenderer; +@protocol InputDelegate; +@protocol RenderDelegate; + +@protocol Tool <RenderDelegate,InputDelegate> +@property (readonly) NSString *name; +@property (readonly) const gchar *stockId; +@property (readonly) NSString *helpText; +@property (readonly) NSString *shortcut; +@property (retain) GraphRenderer *activeRenderer; +@property (readonly) GtkWidget *configurationWidget; +- (void) loadConfiguration:(Configuration*)config; +- (void) saveConfiguration:(Configuration*)config; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/ToolBox.h b/tikzit-1/src/gtk/ToolBox.h new file mode 100644 index 0000000..60074c1 --- /dev/null +++ b/tikzit-1/src/gtk/ToolBox.h @@ -0,0 +1,45 @@ +/* + * Copyright 2012 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 Configuration; +@class Window; +@protocol Tool; + +@interface ToolBox : NSObject { + GtkWidget *window; + GtkToolItemGroup *toolGroup; + GtkWidget *titleLabel; + GtkWidget *configWidgetContainer; + GtkWidget *configWidget; +} + +@property (assign) id<Tool> selectedTool; + +- (id) initWithTools:(NSArray*)tools; + +- (void) show; +- (void) present; +- (void) attachToWindow:(Window*)parent; + +- (void) loadConfiguration:(Configuration*)config; +- (void) saveConfiguration:(Configuration*)config; +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/ToolBox.m b/tikzit-1/src/gtk/ToolBox.m new file mode 100644 index 0000000..c6d2ccf --- /dev/null +++ b/tikzit-1/src/gtk/ToolBox.m @@ -0,0 +1,280 @@ +/* + * Copyright 2012 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 "ToolBox.h" + +#import "Application.h" +#import "Configuration.h" +#import "Tool.h" +#import "Window.h" + +#import "gtkhelpers.h" +#import "tztoolpalette.h" + +static void tool_button_toggled_cb (GtkWidget *widget, ToolBox *toolBox); + +#define TOOL_DATA_KEY "tikzit-tool" + +@implementation ToolBox + +- (id) init { + [self release]; + return nil; +} + +- (id) initWithTools:(NSArray*)tools { + self = [super init]; + + if (self) { + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + g_object_ref_sink (window); + gtk_window_set_title (GTK_WINDOW (window), "Toolbox"); + gtk_window_set_role (GTK_WINDOW (window), "toolbox"); + gtk_window_set_type_hint (GTK_WINDOW (window), + GDK_WINDOW_TYPE_HINT_UTILITY); + gtk_window_set_deletable (GTK_WINDOW (window), FALSE); + + GtkWidget *mainLayout = gtk_vbox_new (FALSE, 5); + gtk_widget_show (mainLayout); + gtk_container_add (GTK_CONTAINER (window), mainLayout); + + GtkWidget *toolPalette = tz_tool_palette_new (); + gtk_widget_show (toolPalette); + gtk_box_pack_start (GTK_BOX (mainLayout), + toolPalette, + FALSE, + FALSE, + 0); + gtk_tool_palette_set_style (GTK_TOOL_PALETTE (toolPalette), + GTK_TOOLBAR_ICONS); + + toolGroup = GTK_TOOL_ITEM_GROUP (gtk_tool_item_group_new ("Tools")); + g_object_ref_sink (G_OBJECT (toolGroup)); + gtk_tool_item_group_set_label_widget ( + toolGroup, + NULL); + gtk_container_add (GTK_CONTAINER (toolPalette), GTK_WIDGET (toolGroup)); + gtk_widget_show (GTK_WIDGET (toolGroup)); + + GSList *item_group = NULL; + for (id<Tool> tool in tools) { + NSString *tooltip = [NSString stringWithFormat: + @"%@: %@ (%@)", + [tool name], [tool helpText], [tool shortcut]]; + GtkToolItem *item = gtk_radio_tool_button_new_from_stock ( + item_group, + [tool stockId]); + gtk_tool_item_set_tooltip_text (item, [tooltip UTF8String]); + item_group = gtk_radio_tool_button_get_group ( + GTK_RADIO_TOOL_BUTTON (item)); + gtk_tool_item_group_insert ( + toolGroup, + item, + -1); + gtk_widget_show (GTK_WIDGET (item)); + g_object_set_data_full ( + G_OBJECT(item), + TOOL_DATA_KEY, + [tool retain], + release_obj); + + g_signal_connect (item, "toggled", + G_CALLBACK (tool_button_toggled_cb), + self); + } + + GtkWidget *sep = gtk_hseparator_new (); + gtk_widget_show (sep); + gtk_box_pack_start (GTK_BOX (mainLayout), + sep, + FALSE, + FALSE, + 0); + + titleLabel = gtk_label_new (""); + g_object_ref_sink (titleLabel); + gtk_widget_show (titleLabel); + + PangoAttrList *attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, + pango_attr_weight_new (PANGO_WEIGHT_SEMIBOLD)); + gtk_label_set_attributes (GTK_LABEL (titleLabel), attrs); + pango_attr_list_unref (attrs); + + gtk_box_pack_start (GTK_BOX (mainLayout), + titleLabel, + FALSE, + FALSE, + 0); + + configWidgetContainer = gtk_alignment_new (0.5, 0.5, 1.0, 1.0); + g_object_ref_sink (configWidgetContainer); + gtk_widget_show (configWidgetContainer); + gtk_box_pack_start (GTK_BOX (mainLayout), + configWidgetContainer, + TRUE, + TRUE, + 0); + gtk_alignment_set_padding (GTK_ALIGNMENT (configWidgetContainer), + 5, 5, 5, 5); + + gint button_width; + gint button_height; + + if (tz_tool_palette_get_button_size (TZ_TOOL_PALETTE (toolPalette), + &button_width, &button_height)) + { + GdkGeometry geometry; + + geometry.min_width = 2 * button_width; + geometry.min_height = -1; + geometry.base_width = button_width; + geometry.base_height = 0; + geometry.width_inc = button_width; + geometry.height_inc = 1; + + gtk_window_set_geometry_hints (GTK_WINDOW (window), + NULL, + &geometry, + GDK_HINT_MIN_SIZE | + GDK_HINT_BASE_SIZE | + GDK_HINT_RESIZE_INC | + GDK_HINT_USER_POS); + } + gtk_window_set_default_size (GTK_WINDOW (window), button_width * 5, 500); + + // hack to position the toolbox window somewhere sensible + // (upper left) + gtk_window_parse_geometry (GTK_WINDOW (window), "+0+0"); + } + + return self; +} + +- (void) dealloc { + if (window) { + g_object_unref (G_OBJECT (toolGroup)); + g_object_unref (G_OBJECT (titleLabel)); + g_object_unref (G_OBJECT (configWidgetContainer)); + if (configWidget) + g_object_unref (G_OBJECT (configWidget)); + gtk_widget_destroy (window); + g_object_unref (G_OBJECT (window)); + } + + [super dealloc]; +} + +- (id<Tool>) selectedTool { + guint count = gtk_tool_item_group_get_n_items (toolGroup); + for (guint i = 0; i < count; ++i) { + GtkToolItem *item = gtk_tool_item_group_get_nth_item (toolGroup, i); + if (gtk_toggle_tool_button_get_active (GTK_TOGGLE_TOOL_BUTTON (item))) { + return (id)g_object_get_data (G_OBJECT (item), TOOL_DATA_KEY); + } + } + return nil; +} + +- (void) _setToolWidget:(GtkWidget*)widget { + if (configWidget) { + gtk_widget_hide (configWidget); + gtk_container_remove (GTK_CONTAINER (configWidgetContainer), + configWidget); + g_object_unref (configWidget); + } + configWidget = widget; + if (configWidget) { + g_object_ref (configWidget); + gtk_container_add (GTK_CONTAINER (configWidgetContainer), + configWidget); + gtk_widget_show (configWidget); + } +} + +- (void) setSelectedTool:(id<Tool>)tool { + guint count = gtk_tool_item_group_get_n_items (toolGroup); + for (guint i = 0; i < count; ++i) { + GtkToolItem *item = gtk_tool_item_group_get_nth_item (toolGroup, i); + id<Tool> data = (id)g_object_get_data (G_OBJECT (item), TOOL_DATA_KEY); + if (data == tool) { + gtk_toggle_tool_button_set_active ( + GTK_TOGGLE_TOOL_BUTTON (item), + TRUE); + break; + } + } + gtk_label_set_label (GTK_LABEL (titleLabel), + [[tool name] UTF8String]); + [self _setToolWidget:[tool configurationWidget]]; +} + +- (void) show { + gtk_widget_show (window); +} + +- (void) present { + gtk_window_present (GTK_WINDOW (window)); +} + +- (void) attachToWindow:(Window*)parent { + utility_window_attach (GTK_WINDOW (window), [parent gtkWindow]); +} + +- (void) loadConfiguration:(Configuration*)config { + if ([config hasGroup:@"ToolBox"]) { + tz_restore_window (GTK_WINDOW (window), + [config integerEntry:@"x" inGroup:@"ToolBox"], + [config integerEntry:@"y" inGroup:@"ToolBox"], + [config integerEntry:@"w" inGroup:@"ToolBox"], + [config integerEntry:@"h" inGroup:@"ToolBox"]); + } +} + +- (void) saveConfiguration:(Configuration*)config { + gint x, y, w, h; + + gtk_window_get_position (GTK_WINDOW (window), &x, &y); + gtk_window_get_size (GTK_WINDOW (window), &w, &h); + + [config setIntegerEntry:@"x" inGroup:@"ToolBox" value:x]; + [config setIntegerEntry:@"y" inGroup:@"ToolBox" value:y]; + [config setIntegerEntry:@"w" inGroup:@"ToolBox" value:w]; + [config setIntegerEntry:@"h" inGroup:@"ToolBox" value:h]; +} + +@end + +static void tool_button_toggled_cb (GtkWidget *widget, ToolBox *toolBox) { + if (gtk_toggle_tool_button_get_active (GTK_TOGGLE_TOOL_BUTTON (widget))) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + id<Tool> tool = (id)g_object_get_data (G_OBJECT(widget), TOOL_DATA_KEY); + [app setActiveTool:tool]; + NSDictionary *userInfo = [NSDictionary + dictionaryWithObject:tool + forKey:@"tool"]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"ToolSelectionChanged" + object:toolBox + userInfo:userInfo]; + + [pool drain]; + } +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/WidgetSurface.h b/tikzit-1/src/gtk/WidgetSurface.h new file mode 100644 index 0000000..667749f --- /dev/null +++ b/tikzit-1/src/gtk/WidgetSurface.h @@ -0,0 +1,54 @@ +/* + * 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 <InputDelegate.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> inputDelegate; + BOOL keepCentered; + BOOL buttonPressesRequired; + CGFloat defaultScale; + NSSize lastKnownSize; +} + +@property (assign) BOOL canFocus; +@property (assign) BOOL keepCentered; +@property (assign) CGFloat defaultScale; + +- (id) initWithWidget:(GtkWidget*)widget; +- (GtkWidget*) widget; + +- (id<InputDelegate>) inputDelegate; +- (void) setInputDelegate:(id<InputDelegate>)delegate; + +/** + * Set the minimum size that this widget wants + */ +- (void) setSizeRequestWidth:(double)width height:(double)height; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/WidgetSurface.m b/tikzit-1/src/gtk/WidgetSurface.m new file mode 100644 index 0000000..004e722 --- /dev/null +++ b/tikzit-1/src/gtk/WidgetSurface.m @@ -0,0 +1,630 @@ +/* + * 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); +static void set_cursor (GtkWidget *widget, GdkCursor *cursor); +static void unref_cursor (gpointer cursor, GClosure *closure); +// }}} + +@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; +- (void) addToEventMask:(GdkEventMask)values; +- (void) removeFromEventMask:(GdkEventMask)values; +@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)); + 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]; + if ([self canFocus]) { + [self addToEventMask:GDK_BUTTON_PRESS_MASK]; + } else { + [self removeFromEventMask:GDK_BUTTON_PRESS_MASK]; + } + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [transformer release]; + g_object_unref (G_OBJECT (widget)); + + [super dealloc]; +} + +- (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) 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>) inputDelegate { + return inputDelegate; +} + +- (void) setInputDelegate:(id<InputDelegate>)delegate { + if (delegate == inputDelegate) { + return; + } + buttonPressesRequired = NO; + 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:)]) { + buttonPressesRequired = YES; + mask |= GDK_BUTTON_PRESS_MASK; + } + if ([delegate respondsToSelector:@selector(mouseReleaseAt:withButton:andMask:)]) { + mask |= GDK_BUTTON_RELEASE_MASK; + } + if ([delegate respondsToSelector:@selector(mouseDoubleClickAt:withButton:andMask:)]) { + buttonPressesRequired = YES; + 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) canFocus { + return gtk_widget_get_can_focus (widget); +} + +- (void) setCanFocus:(BOOL)focus { + gtk_widget_set_can_focus (widget, focus); + if (focus) { + [self addToEventMask:GDK_BUTTON_PRESS_MASK]; + } else if (!buttonPressesRequired) { + [self removeFromEventMask:GDK_BUTTON_PRESS_MASK]; + } +} + +- (BOOL) hasFocus { + return gtk_widget_has_focus (widget); +} + +- (void) renderFocus { + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + int width = 0; + int height = 0; + gdk_drawable_get_size (window, &width, &height); + gtk_paint_focus (gtk_widget_get_style (widget), + window, + GTK_STATE_NORMAL, + NULL, + widget, + NULL, + 0, + 0, + width, + height + ); + } +} + +- (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) setCursor:(Cursor)c { + GdkCursor *cursor = NULL; + switch (c) { + case ResizeRightCursor: + cursor = gdk_cursor_new (GDK_RIGHT_SIDE); + break; + case ResizeBottomRightCursor: + cursor = gdk_cursor_new (GDK_BOTTOM_RIGHT_CORNER); + break; + case ResizeBottomCursor: + cursor = gdk_cursor_new (GDK_BOTTOM_SIDE); + break; + case ResizeBottomLeftCursor: + cursor = gdk_cursor_new (GDK_BOTTOM_LEFT_CORNER); + break; + case ResizeLeftCursor: + cursor = gdk_cursor_new (GDK_LEFT_SIDE); + break; + case ResizeTopLeftCursor: + cursor = gdk_cursor_new (GDK_TOP_LEFT_CORNER); + break; + case ResizeTopCursor: + cursor = gdk_cursor_new (GDK_TOP_SIDE); + break; + case ResizeTopRightCursor: + cursor = gdk_cursor_new (GDK_TOP_RIGHT_CORNER); + break; + default: break; + } + GdkWindow *window = gtk_widget_get_window (widget); + g_signal_handlers_disconnect_matched (window, + G_SIGNAL_MATCH_FUNC, 0, 0, NULL, + G_CALLBACK (set_cursor), NULL); + if (window) { + gdk_window_set_cursor (window, cursor); + if (cursor != NULL) { + gdk_cursor_unref (cursor); + } + } else { + g_signal_connect_data (widget, + "realize", G_CALLBACK (set_cursor), cursor, + unref_cursor, 0); + } +} + +@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]; +} + +- (void) addToEventMask:(GdkEventMask)values { + GdkEventMask mask; + g_object_get (G_OBJECT (widget), "events", &mask, NULL); + mask |= values; + g_object_set (G_OBJECT (widget), "events", mask, NULL); +} + +- (void) removeFromEventMask:(GdkEventMask)values { + GdkEventMask mask; + g_object_get (G_OBJECT (widget), "events", &mask, NULL); + mask ^= values; + if (buttonPressesRequired || [self canFocus]) { + mask |= GDK_BUTTON_PRESS_MASK; + } + g_object_set (G_OBJECT (widget), "events", mask, NULL); +} + +@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 canFocus]) { + if (!gtk_widget_has_focus (widget)) { + gtk_widget_grab_focus (widget); + } + } + + id<InputDelegate> 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<InputDelegate> 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<InputDelegate> 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<InputDelegate> 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<InputDelegate> 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<InputDelegate> 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; +} + +static void unref_cursor (gpointer cursor, GClosure *closure) { + if (cursor != NULL) { + gdk_cursor_unref ((GdkCursor*)cursor); + } +} + +static void set_cursor (GtkWidget *widget, GdkCursor *cursor) { + GdkWindow *window = gtk_widget_get_window (widget); + if (window) { + gdk_window_set_cursor (window, cursor); + if (cursor != NULL) { + gdk_cursor_unref (cursor); + } + } +} +// }}} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/Window.h b/tikzit-1/src/gtk/Window.h new file mode 100644 index 0000000..a3ce8a4 --- /dev/null +++ b/tikzit-1/src/gtk/Window.h @@ -0,0 +1,182 @@ +/* + * Copyright 2011-2012 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 GraphEditorPanel; +@class Menu; +@class PropertyPane; +@class Preambles; +@class PreambleEditor; +@class PreviewWindow; +@class SettingsDialog; +@class StyleManager; +@class StylesPane; +@class TikzDocument; +@protocol Tool; + +/** + * Manages a document window + */ +@interface Window: NSObject { + // GTK+ widgets + GtkWindow *window; + GtkTextBuffer *tikzBuffer; + GtkStatusbar *statusBar; + GtkPaned *tikzPaneSplitter; + GtkWidget *tikzPane; + + gulong clipboard_handler_id; + GtkTextTag *errorHighlightTag; // owned by tikzBuffer + + // Classes that manage parts of the window + Menu *menu; + GraphEditorPanel *graphPanel; + + PreviewWindow *previewWindow; + + // state variables + BOOL suppressTikzUpdates; + BOOL hasParseError; + + // the document displayed by the window + TikzDocument *document; +} + +/** + * The document displayed by the window + */ +@property (retain) TikzDocument *document; +@property (readonly) BOOL hasFocus; +@property (readonly) GtkWindow *gtkWindow; + +/** + * Create a window with an empty document + */ +- (id) init; ++ (id) window; + +/** + * Create a window with the given document + */ +- (id) initWithDocument:(TikzDocument*)doc; ++ (id) windowWithDocument:(TikzDocument*)doc; + +/** + * Present the window to the user + */ +- (void) present; + +/** + * Open a file, asking the user which file to open + */ +- (void) openFile; +/** + * Open a file + */ +- (BOOL) openFileAtPath:(NSString*)path; +/** + * Save the active document to the path it was opened from + * or last saved to, or ask the user where to save it. + */ +- (BOOL) saveActiveDocument; +/** + * Save the active document, asking the user where to save it. + */ +- (BOOL) saveActiveDocumentAs; +/** + * Save the active document as a shape, asking the user what to name it. + */ +- (void) saveActiveDocumentAsShape; + +/** + * Close the window. + * + * May terminate the application if this is the last window. + * + * Will ask for user confirmation if the document is not saved. + */ +- (void) close; + +/** + * Cut the current selection to the clipboard. + */ +- (void) selectionCutToClipboard; +/** + * Copy the current selection to the clipboard. + */ +- (void) selectionCopyToClipboard; +/** + * Paste from the clipboard to the appropriate place. + */ +- (void) pasteFromClipboard; + +/** + * The GTK+ window that this class manages. + */ +- (GtkWindow*) gtkWindow; +/** + * The menu for the window. + */ +- (Menu*) menu; + +/** + * Present an error to the user + * + * @param error the error to present + */ +- (void) presentError:(NSError*)error; +/** + * Present an error to the user + * + * @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 + * + * @param error the error to present + */ +- (void) presentGError:(GError*)error; +/** + * Present an error to the user + * + * @param error the error to present + * @param message a message to display with the error + */ +- (void) presentGError:(GError*)error withMessage:(NSString*)message; + +- (void) setActiveTool:(id<Tool>)tool; + +- (void) zoomIn; +- (void) zoomOut; +- (void) zoomReset; + +/** + * Show or update the preview window. + */ +- (void) presentPreview; +/** + * Show or update the preview window without it grabbing focus + */ +- (void) updatePreview; + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/Window.m b/tikzit-1/src/gtk/Window.m new file mode 100644 index 0000000..2d9e63a --- /dev/null +++ b/tikzit-1/src/gtk/Window.m @@ -0,0 +1,991 @@ +/* + * Copyright 2011-2012 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 "Window.h" + +#import <gtk/gtk.h> +#import "gtkhelpers.h" +#import "clipboard.h" + +#import "Application.h" +#import "Configuration.h" +#import "FileChooserDialog.h" +#import "GraphEditorPanel.h" +#import "Menu.h" +#import "RecentManager.h" +#import "Shape.h" +#import "SupportDir.h" +#import "TikzDocument.h" + +#ifdef HAVE_POPPLER +#import "PreviewWindow.h" +#endif + +enum { + GraphInfoStatus, + ParseStatus +}; + +// {{{ 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 window_toplevel_focus_changed_cb (GObject *gobject, GParamSpec *pspec, Window *window); +static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, Window *window); +static void tikz_buffer_changed_cb (GtkTextBuffer *buffer, Window *window); +static gboolean main_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, Window *window); +static void main_window_destroy_cb (GtkWidget *widget, Window *window); +static gboolean main_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, Window *window); +static void update_paste_action (GtkClipboard *clipboard, GdkEvent *event, GtkAction *action); + +// }}} + +@interface Window (Notifications) +- (void) tikzBufferChanged; +- (void) windowSizeChangedWidth:(int)width height:(int)height; +- (void) documentTikzChanged:(NSNotification*)notification; +- (void) documentSelectionChanged:(NSNotification*)notification; +- (void) undoStackChanged:(NSNotification*)notification; +@end + +@interface Window (InitHelpers) +- (void) _loadUi; +- (void) _restoreUiState; +- (void) _connectSignals; +@end + +@interface Window (Private) <PreviewHandler> +- (BOOL) _askCanClose; +/** Open a document, dealing with errors as necessary */ +- (TikzDocument*) _openDocument:(NSString*)path; +- (void) _placeGraphOnClipboard:(Graph*)graph; +- (void) _clearParseError; +- (void) _setParseError:(NSError*)error; +/** 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; +- (void) showPreview; +@end + +// }}} +// {{{ API + +@implementation Window + +@synthesize gtkWindow=window; + +- (id) init { + return [self initWithDocument:[TikzDocument documentWithStyleManager:[app styleManager]]]; +} ++ (id) window { + return [[[self alloc] init] autorelease]; +} +- (id) initWithDocument:(TikzDocument*)doc { + self = [super init]; + + if (self) { + [self _loadUi]; + [self _restoreUiState]; + [self _connectSignals]; + + [self setDocument:doc]; + + gtk_widget_show (GTK_WIDGET (window)); + } + + return self; +} ++ (id) windowWithDocument:(TikzDocument*)doc { + return [[[self alloc] initWithDocument:doc] autorelease]; +} + +- (void) dealloc { + // The GTK+ window has already been destroyed at this point + + [[NSNotificationCenter defaultCenter] removeObserver:self]; + g_signal_handler_disconnect ( + gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), + clipboard_handler_id); + + [previewWindow release]; + [menu release]; + [graphPanel release]; + [document release]; + + g_object_unref (tikzBuffer); + g_object_unref (tikzPane); + g_object_unref (tikzPaneSplitter); + g_object_unref (statusBar); + g_object_unref (window); + + [super dealloc]; +} + +- (TikzDocument*) document { + return document; +} +- (void) setDocument:(TikzDocument*)newDoc { + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:[document pickSupport]]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:document]; + + TikzDocument *oldDoc = document; + document = [newDoc retain]; + + [graphPanel setDocument:document]; + [previewWindow setDocument:document]; + [self _updateTikz]; + [self _updateTitle]; + [self _updateStatus]; + [self _updateUndoActions]; + [menu notifySelectionChanged:[document pickSupport]]; + [[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]]; + + if ([document path] != nil) { + [[RecentManager defaultManager] addRecentFile:[document path]]; + } + + NSDictionary *userInfo; + userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + document, @"document", + oldDoc, @"oldDocument", + nil]; + [[NSNotificationCenter defaultCenter] + postNotificationName:@"DocumentChanged" + object:self + userInfo:userInfo]; + [oldDoc release]; +} + +- (BOOL) hasFocus { + return gtk_window_has_toplevel_focus (GTK_WINDOW (window)); +} + +- (void) present { + gtk_window_present (GTK_WINDOW (window)); +} + +- (void) openFile { + FileChooserDialog *dialog = [FileChooserDialog openDialogWithParent:window]; + [dialog addStandardFilters]; + if ([document path]) { + [dialog setCurrentFolder:[[document path] stringByDeletingLastPathComponent]]; + } else if ([app lastOpenFolder]) { + [dialog setCurrentFolder:[app lastOpenFolder]]; + } + + if ([dialog showDialog]) { + if ([self openFileAtPath:[dialog filePath]]) { + [app setLastOpenFolder:[dialog currentFolder]]; + } + } + [dialog destroy]; +} + +- (BOOL) openFileAtPath:(NSString*)path { + TikzDocument *doc = [self _openDocument:path]; + if (doc != nil) { + if (![document hasUnsavedChanges] && [document path] == nil) { + // we just have a fresh, untitled document - replace it + [self setDocument:doc]; + } else { + [app newWindowWithDocument:doc]; + } + return YES; + } + return NO; +} + +- (BOOL) saveActiveDocument { + if ([document path] == nil) { + return [self saveActiveDocumentAs]; + } else { + NSError *error = nil; + if (![document save:&error]) { + [self presentError:error]; + return NO; + } else { + [self _updateTitle]; + return YES; + } + } +} + +- (BOOL) saveActiveDocumentAs { + FileChooserDialog *dialog = [FileChooserDialog saveDialogWithParent:window]; + [dialog addStandardFilters]; + if ([document path] != nil) { + [dialog setCurrentFolder:[[document path] stringByDeletingLastPathComponent]]; + } else if ([app lastSaveAsFolder] != nil) { + [dialog setCurrentFolder:[app lastSaveAsFolder]]; + } + [dialog setSuggestedName:[document suggestedFileName]]; + + BOOL saved = NO; + 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]; + [app setLastSaveAsFolder:[dialog currentFolder]]; + saved = YES; + } + } + [dialog destroy]; + return saved; +} + +- (void) saveActiveDocumentAsShape { + GtkWidget *dialog = gtk_dialog_new_with_buttons ( + "Save as shape", + window, + 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 (window, + 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 (window, + 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 { + if (![document saveCopyToPath:file error:&error]) { + [self presentError:error withMessage:@"Could not save shape file"]; + } else { + [Shape refreshShapeDictionary]; + } + } + } + } + gtk_widget_destroy (dialog); +} + +- (void) close { + if ([self _askCanClose]) { + gtk_widget_destroy (GTK_WIDGET (window)); + } +} + +- (void) selectionCutToClipboard { + if ([[[document pickSupport] selectedNodes] count] > 0) { + [self _placeGraphOnClipboard:[document selectionCut]]; + } +} + +- (void) selectionCopyToClipboard { + if ([[[document pickSupport] selectedNodes] count] > 0) { + [self _placeGraphOnClipboard:[document selectionCopy]]; + } +} + +- (void) pasteFromClipboard { + gtk_clipboard_request_contents (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), + tikzit_picture_atom, + clipboard_paste_contents, + document); +} + +- (GtkWindow*) gtkWindow { + return window; +} + +- (Configuration*) mainConfiguration { + return [app mainConfiguration]; +} + +- (Menu*) menu { + return menu; +} + +- (void) presentError:(NSError*)error { + const gchar *errorDesc = "unknown error"; + if (error && [error localizedDescription]) { + errorDesc = [[error localizedDescription] UTF8String]; + } + GtkWidget *dialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", + errorDesc); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) presentError:(NSError*)error withMessage:(NSString*)message { + const gchar *errorDesc = "unknown error"; + if (error && [error localizedDescription]) { + errorDesc = [[error localizedDescription] UTF8String]; + } + GtkWidget *dialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s: %s", + [message UTF8String], + errorDesc); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) presentGError:(GError*)error { + const gchar *errorDesc = "unknown error"; + if (error && error->message) { + errorDesc = error->message; + } + GtkWidget *dialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s", + errorDesc); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) presentGError:(GError*)error withMessage:(NSString*)message { + const gchar *errorDesc = "unknown error"; + if (error && error->message) { + errorDesc = error->message; + } + GtkWidget *dialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_CLOSE, + "%s: %s", + [message UTF8String], + errorDesc); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +- (void) setActiveTool:(id<Tool>)tool { + [graphPanel setActiveTool:tool]; + gboolean hasfocus; + g_object_get (G_OBJECT (window), "has-toplevel-focus", &hasfocus, NULL); + if (hasfocus) { + [graphPanel grabTool]; + } +} + +- (void) zoomIn { + [graphPanel zoomIn]; +} + +- (void) zoomOut { + [graphPanel zoomOut]; +} + +- (void) zoomReset { + [graphPanel zoomReset]; +} + +- (void) presentPreview { +#ifdef HAVE_POPPLER + if (previewWindow == nil) { + previewWindow = [[PreviewWindow alloc] initWithPreambles:[app preambles] + config:[app mainConfiguration]]; + //[previewWindow setParentWindow:self]; + [previewWindow setDocument:document]; + } + [previewWindow present]; +#endif +} + +- (void) updatePreview { +#ifdef HAVE_POPPLER + if (previewWindow == nil) { + previewWindow = [[PreviewWindow alloc] initWithPreambles:[app preambles] + config:[app mainConfiguration]]; + //[previewWindow setParentWindow:self]; + [previewWindow setDocument:document]; + } + [previewWindow show]; +#endif +} + +@end + +// }}} +// {{{ Notifications + +@implementation Window (Notifications) +- (void) graphHeightChanged:(int)newHeight { + [[app mainConfiguration] setIntegerEntry:@"graphHeight" + inGroup:@"window" + 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); + + NSError *error = nil; + BOOL success = [document updateTikz:[NSString stringWithUTF8String:text] error:&error]; + if (success) + [self _clearParseError]; + else + [self _setParseError:error]; + + 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]; + [[app mainConfiguration] setIntegerListEntry:@"windowSize" + inGroup:@"window" + 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 Window (InitHelpers) + +- (void) _loadUi { + window = GTK_WINDOW (gtk_window_new (GTK_WINDOW_TOPLEVEL)); + g_object_ref_sink (window); + gtk_window_set_title (window, "TikZiT"); + gtk_window_set_default_size (window, 700, 400); + + GtkBox *mainLayout = GTK_BOX (gtk_vbox_new (FALSE, 0)); + gtk_widget_show (GTK_WIDGET (mainLayout)); + gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (mainLayout)); + + menu = [[Menu alloc] initForWindow: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); + + tikzPaneSplitter = GTK_PANED (gtk_vpaned_new ()); + g_object_ref_sink (tikzPaneSplitter); + gtk_widget_show (GTK_WIDGET (tikzPaneSplitter)); + gtk_box_pack_start (mainLayout, GTK_WIDGET (tikzPaneSplitter), TRUE, TRUE, 0); + + graphPanel = [[GraphEditorPanel alloc] initWithDocument:document]; + [graphPanel setPreviewHandler:self]; + GtkWidget *graphEditorWidget = [graphPanel widget]; + gtk_widget_show (graphEditorWidget); + GtkWidget *graphFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (graphFrame), graphEditorWidget); + gtk_widget_show (graphFrame); + gtk_paned_pack1 (tikzPaneSplitter, graphFrame, TRUE, TRUE); + + tikzBuffer = gtk_text_buffer_new (NULL); + g_object_ref_sink (tikzBuffer); + errorHighlightTag = gtk_text_buffer_create_tag ( + tikzBuffer, NULL, + "foreground", "#d40000", + "foreground-set", TRUE, + "weight", PANGO_WEIGHT_SEMIBOLD, + "weight-set", TRUE, + NULL); + GtkWidget *tikzScroller = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (tikzScroller); + + tikzPane = gtk_text_view_new_with_buffer (tikzBuffer); + gtk_text_view_set_left_margin (GTK_TEXT_VIEW (tikzPane), 3); + gtk_text_view_set_right_margin (GTK_TEXT_VIEW (tikzPane), 3); + g_object_ref_sink (tikzPane); + gtk_widget_show (tikzPane); + gtk_container_add (GTK_CONTAINER (tikzScroller), tikzPane); + GtkWidget *tikzFrame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (tikzFrame), tikzScroller); + gtk_widget_show (tikzFrame); + gtk_paned_pack2 (tikzPaneSplitter, tikzFrame, FALSE, TRUE); + + statusBar = GTK_STATUSBAR (gtk_statusbar_new ()); + g_object_ref_sink (statusBar); + 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 { + Configuration *config = [app mainConfiguration]; + NSArray *windowSize = [config integerListEntry:@"windowSize" + inGroup:@"window"]; + 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 (window, width, height); + } + } + int panePos = [config integerEntry:@"graphHeight" + inGroup:@"window"]; + if (panePos > 0) { + gtk_paned_set_position (tikzPaneSplitter, panePos); + } +} + +- (void) _connectSignals { + GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + clipboard_handler_id = + g_signal_connect (G_OBJECT (clipboard), + "owner-change", + G_CALLBACK (update_paste_action), + [menu pasteAction]); + g_signal_connect (G_OBJECT (window), + "key-press-event", + G_CALLBACK (tz_hijack_key_press), + NULL); + g_signal_connect (G_OBJECT (window), + "notify::has-toplevel-focus", + G_CALLBACK (window_toplevel_focus_changed_cb), + self); + g_signal_connect (G_OBJECT (tikzPaneSplitter), + "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 (window), + "delete-event", + G_CALLBACK (main_window_delete_event_cb), + self); + g_signal_connect (G_OBJECT (window), + "destroy", + G_CALLBACK (main_window_destroy_cb), + self); + g_signal_connect (G_OBJECT (window), + "configure-event", + G_CALLBACK (main_window_configure_event_cb), + self); +} +@end + +// }}} +// {{{ Private + +@implementation Window (Private) + +- (BOOL) _askCanClose { + if ([document hasUnsavedChanges]) { + GtkWidget *dialog = gtk_message_dialog_new (window, + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_NONE, + "Save changes to the document \"%s\" before closing?", + [[document name] UTF8String]); + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + "Save", GTK_RESPONSE_YES, + "Don't save", GTK_RESPONSE_NO, + "Cancel", GTK_RESPONSE_CANCEL, + NULL); + gint result = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + if (result == GTK_RESPONSE_YES) { + return [self saveActiveDocument]; + } else { + return result == GTK_RESPONSE_NO; + } + } else { + return YES; + } +} + +- (TikzDocument*) _openDocument:(NSString*)path { + NSError *error = nil; + TikzDocument *d = [TikzDocument documentFromFile:path + styleManager:[app styleManager] + error:&error]; + if (d != nil) { + return d; + } else { + if ([error code] == TZ_ERR_PARSE) { + [self presentError:error withMessage:@"Invalid file"]; + } else { + [self presentError:error withMessage:@"Could not open file"]; + } + [[RecentManager defaultManager] removeRecentFile:path]; + return nil; + } +} + +- (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) _clearParseError { + if (!hasParseError) + return; + gtk_statusbar_pop (statusBar, ParseStatus); + text_buffer_clear_tag (tikzBuffer, errorHighlightTag); + widget_clear_error (tikzPane); + hasParseError = NO; +} + +- (void) _setParseError:(NSError*)error { + if (!hasParseError) { + widget_set_error (tikzPane); + hasParseError = YES; + } + NSString *message = [NSString stringWithFormat:@"Parse error: %@", [error localizedDescription]]; + gtk_statusbar_pop (statusBar, ParseStatus); + gtk_statusbar_push (statusBar, ParseStatus, [message UTF8String]); + + text_buffer_clear_tag (tikzBuffer, errorHighlightTag); + + NSDictionary *errorInfo = [error userInfo]; + if ([errorInfo objectForKey:@"startLine"] != nil) { + GtkTextIter symbolStart; + GtkTextIter symbolEnd; + gtk_text_buffer_get_iter_at_line_index (tikzBuffer, &symbolStart, + [[errorInfo objectForKey:@"startLine"] intValue] - 1, + [[errorInfo objectForKey:@"startColumn"] intValue] - 1); + gtk_text_buffer_get_iter_at_line_index (tikzBuffer, &symbolEnd, + [[errorInfo objectForKey:@"endLine"] intValue] - 1, + [[errorInfo objectForKey:@"endColumn"] intValue]); + gtk_text_buffer_apply_tag (tikzBuffer, errorHighlightTag, + &symbolStart, &symbolEnd); + } +} + +- (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(window, [title UTF8String]); +} + +- (void) _updateStatus { + // FIXME: show tooltips or something instead + 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, GraphInfoStatus); + gtk_statusbar_push(statusBar, GraphInfoStatus, 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 _clearParseError]; + + suppressTikzUpdates = FALSE; + } +} + +- (GraphEditorPanel*) _graphPanel { + return graphPanel; +} + +- (void) showPreview { + [self updatePreview]; +} + +@end + +// }}} +// {{{ GTK+ callbacks + +static void window_toplevel_focus_changed_cb (GObject *gobject, GParamSpec *pspec, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + gboolean hasfocus; + g_object_get (gobject, "has-toplevel-focus", &hasfocus, NULL); + if (hasfocus) { + [[NSNotificationCenter defaultCenter] + postNotificationName:@"WindowGainedFocus" + object:window]; + [[window _graphPanel] grabTool]; + } else { + [[NSNotificationCenter defaultCenter] + postNotificationName:@"WindowLostFocus" + object:window]; + } + [pool drain]; +} + +static void graph_divider_position_changed_cb (GObject *gobject, GParamSpec *pspec, Window *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, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window tikzBufferChanged]; + [pool drain]; +} + +static gboolean main_window_delete_event_cb (GtkWidget *widget, GdkEvent *event, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [window close]; + [pool drain]; + return TRUE; +} + +static void main_window_destroy_cb (GtkWidget *widget, Window *window) { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [[NSNotificationCenter defaultCenter] postNotificationName:@"WindowClosed" + object:window]; + [pool drain]; +} + +static gboolean main_window_configure_event_cb (GtkWidget *widget, GdkEventConfigure *event, Window *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-1/src/gtk/cairo_helpers.h b/tikzit-1/src/gtk/cairo_helpers.h new file mode 100644 index 0000000..e95357b --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/cairo_helpers.m b/tikzit-1/src/gtk/cairo_helpers.m new file mode 100644 index 0000000..104e686 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/clipboard.h b/tikzit-1/src/gtk/clipboard.h new file mode 100644 index 0000000..568fc50 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/clipboard.m b/tikzit-1/src/gtk/clipboard.m new file mode 100644 index 0000000..7001717 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/gtkhelpers.h b/tikzit-1/src/gtk/gtkhelpers.h new file mode 100644 index 0000000..e4b79b8 --- /dev/null +++ b/tikzit-1/src/gtk/gtkhelpers.h @@ -0,0 +1,60 @@ +// +// 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> +#import <gdk-pixbuf/gdk-pixbuf.h> + +/** + * Releases the Objective-C object pointed to by data + * + * Intended for use as a cleanup function in Glib/GObject-based + * code. + */ +void release_obj (gpointer data); + +NSString * gtk_editable_get_string (GtkEditable *editable, gint start, gint end); + +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); + +// Equivalent of GTK+3's gdk_pixbuf_get_from_surface() +GdkPixbuf * pixbuf_get_from_surface(cairo_surface_t *surface); + +void tz_restore_window (GtkWindow *window, gint x, gint y, gint w, gint h); + +void label_set_bold (GtkLabel *label); + +void widget_set_error (GtkWidget *widget); +void widget_clear_error (GtkWidget *widget); + +void text_buffer_clear_tag (GtkTextBuffer *buffer, GtkTextTag *tag); + +void utility_window_attach (GtkWindow *util_win, GtkWindow *parent_win); + +// vim:ft=objc:sts=2:sw=2:et diff --git a/tikzit-1/src/gtk/gtkhelpers.m b/tikzit-1/src/gtk/gtkhelpers.m new file mode 100644 index 0000000..9d26af5 --- /dev/null +++ b/tikzit-1/src/gtk/gtkhelpers.m @@ -0,0 +1,275 @@ +// +// 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 release_obj (gpointer data) { + id obj = (id)data; + [obj release]; +} + +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; +} + +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.). + * + * 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; +} + +GdkPixbuf * pixbuf_get_from_surface(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; +} + +/* This function mostly lifted from + * gtk+/gdk/gdkscreen.c:gdk_screen_get_monitor_at_window() + */ +static gint +get_appropriate_monitor (GdkScreen *screen, + gint x, + gint y, + gint w, + gint h) +{ + GdkRectangle rect; + gint area = 0; + gint monitor = -1; + gint num_monitors; + gint i; + + rect.x = x; + rect.y = y; + rect.width = w; + rect.height = h; + + num_monitors = gdk_screen_get_n_monitors (screen); + + for (i = 0; i < num_monitors; i++) + { + GdkRectangle geometry; + + gdk_screen_get_monitor_geometry (screen, i, &geometry); + + if (gdk_rectangle_intersect (&rect, &geometry, &geometry) && + geometry.width * geometry.height > area) + { + area = geometry.width * geometry.height; + monitor = i; + } + } + + if (monitor >= 0) + return monitor; + else + return gdk_screen_get_monitor_at_point (screen, + rect.x + rect.width / 2, + rect.y + rect.height / 2); +} + +/* This function mostly lifted from gimp_session_info_apply_geometry + * in gimp-2.6/app/widgets/gimpsessioninfo.c + */ +void tz_restore_window (GtkWindow *window, gint x, gint y, gint w, gint h) +{ + gint forced_w = w; + gint forced_h = h; + if (w <= 0 || h <= 0) { + gtk_window_get_default_size (window, &w, &h); + } + if (w <= 0 || h <= 0) { + gtk_window_get_size (window, &w, &h); + } + + GdkScreen *screen = gtk_widget_get_screen (GTK_WIDGET (window)); + + gint monitor = 0; + if (w > 0 && h > 0) { + monitor = get_appropriate_monitor (screen, x, y, w, h); + } else { + monitor = gdk_screen_get_monitor_at_point (screen, x, y); + } + + GdkRectangle rect; + gdk_screen_get_monitor_geometry (screen, monitor, &rect); + + x = CLAMP (x, + rect.x, + rect.x + rect.width - (w > 0 ? w : 128)); + y = CLAMP (y, + rect.y, + rect.y + rect.height - (h > 0 ? h : 128)); + + gchar geom[32]; + g_snprintf (geom, sizeof (geom), "%+d%+d", x, y); + + gtk_window_parse_geometry (window, geom); + + if (forced_w > 0 && forced_h > 0) { + gtk_window_set_default_size (window, forced_w, forced_h); + } +} + +void label_set_bold (GtkLabel *label) { + PangoAttrList *attrs = pango_attr_list_new (); + pango_attr_list_insert (attrs, + pango_attr_weight_new (PANGO_WEIGHT_SEMIBOLD)); + gtk_label_set_attributes (label, attrs); + pango_attr_list_unref (attrs); +} + +void widget_set_error (GtkWidget *widget) { + GdkColor color = {0, 65535, 61184, 61184}; + gtk_widget_modify_base (widget, GTK_STATE_NORMAL, &color); +} + +void widget_clear_error (GtkWidget *widget) { + gtk_widget_modify_base (widget, GTK_STATE_NORMAL, NULL); +} + +void text_buffer_clear_tag (GtkTextBuffer *buffer, GtkTextTag *tag) { + GtkTextIter start; + GtkTextIter end; + gtk_text_buffer_get_start_iter (buffer, &start); + gtk_text_buffer_get_end_iter (buffer, &end); + gtk_text_buffer_remove_tag (buffer, tag, &start, &end); +} + +void utility_window_attach (GtkWindow *util_win, GtkWindow *parent_win) { + if (parent_win == gtk_window_get_transient_for (util_win)) + return; + + // HACK: X window managers tend to move windows around when they are + // unmapped and mapped again, so we save the position + gint x, y; + gtk_window_get_position (util_win, &x, &y); + + // HACK: Altering WM_TRANSIENT_FOR on a non-hidden but unmapped window + // (eg: when you have minimised the original parent window) can + // cause the window to be lost forever, so we hide it first + gtk_widget_hide (GTK_WIDGET (util_win)); + gtk_window_set_focus_on_map (util_win, FALSE); + gtk_window_set_transient_for (util_win, parent_win); + gtk_widget_show (GTK_WIDGET (util_win)); + + // HACK: see above + gtk_window_move (util_win, x, y); +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/logo.h b/tikzit-1/src/gtk/logo.h new file mode 100644 index 0000000..e778da9 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/logo.m b/tikzit-1/src/gtk/logo.m new file mode 100644 index 0000000..57533c7 --- /dev/null +++ b/tikzit-1/src/gtk/logo.m @@ -0,0 +1,64 @@ +/* + * 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-1/src/gtk/main.m b/tikzit-1/src/gtk/main.m new file mode 100644 index 0000000..5d9f4a4 --- /dev/null +++ b/tikzit-1/src/gtk/main.m @@ -0,0 +1,111 @@ +// +// 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 "tzstockitems.h" + +#import "Application.h" + +static GOptionEntry entries[] = +{ + //{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Be verbose", NULL }, + { NULL } +}; + +void onUncaughtException(NSException* exception) +{ + NSString *joinStr = @"\n "; + NSLog(@"uncaught exception: %@\n backtrace: %@", + [exception description], + [[exception callStackSymbols] componentsJoinedByString:joinStr]); +} + +int main (int argc, char *argv[]) { + NSSetUncaughtExceptionHandler(&onUncaughtException); + + [[NSAutoreleasePool alloc] init]; + + GError *error = NULL; + GOptionContext *context; + context = g_option_context_new ("[FILES] - PGF/TikZ-based graph editor"); + g_option_context_add_main_entries (context, entries, NULL); + g_option_context_add_group (context, gtk_get_option_group (TRUE)); + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + if (error->domain == G_OPTION_ERROR) { + g_print ("%s\nUse --help to see available options\n", error->message); + } else { + g_print ("Unexpected error parsing options: %s\n", error->message); + } + exit (1); + } + g_option_context_free (context); + +#ifndef WINDOWS + GList *icon_list = NULL; + icon_list = g_list_prepend (icon_list, get_logo(LOGO_SIZE_128)); + icon_list = g_list_prepend (icon_list, get_logo(LOGO_SIZE_64)); + icon_list = g_list_prepend (icon_list, get_logo(LOGO_SIZE_48)); + icon_list = g_list_prepend (icon_list, get_logo(LOGO_SIZE_32)); + icon_list = g_list_prepend (icon_list, get_logo(LOGO_SIZE_24)); + icon_list = 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 + + NSAutoreleasePool *initPool = [[NSAutoreleasePool alloc] init]; + + tz_register_stock_items(); + clipboard_init(); + + Application *app = nil; + if (argc > 1) { + NSMutableArray *files = [NSMutableArray arrayWithCapacity:argc-1]; + for (int i = 1; i < argc; ++i) { + [files insertObject:[NSString stringWithGlibFilename:argv[i]] + atIndex:i-1]; + } + NSLog(@"Files: %@", files); + app = [[Application alloc] initWithFiles:files]; + } else { + app = [[Application alloc] init]; + } + + [initPool drain]; + + gtk_main (); + + [app saveConfiguration]; + [app release]; + + return 0; +} + +// vim:ft=objc:et:sts=4:sw=4 diff --git a/tikzit-1/src/gtk/mkdtemp.h b/tikzit-1/src/gtk/mkdtemp.h new file mode 100644 index 0000000..65ee99e --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/mkdtemp.m b/tikzit-1/src/gtk/mkdtemp.m new file mode 100644 index 0000000..ee3cd7c --- /dev/null +++ b/tikzit-1/src/gtk/mkdtemp.m @@ -0,0 +1,180 @@ +/* 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, + see <http://www.gnu.org/licenses/>. + */ + +/* 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 "stat.h" + +#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-1/src/gtk/stat.h b/tikzit-1/src/gtk/stat.h new file mode 100644 index 0000000..a9829ae --- /dev/null +++ b/tikzit-1/src/gtk/stat.h @@ -0,0 +1,25 @@ +#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 diff --git a/tikzit-1/src/gtk/test/gtk.m b/tikzit-1/src/gtk/test/gtk.m new file mode 100644 index 0000000..aabb0f2 --- /dev/null +++ b/tikzit-1/src/gtk/test/gtk.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 testGtk() { + +} diff --git a/tikzit-1/src/gtk/test/main.m b/tikzit-1/src/gtk/test/main.m new file mode 100644 index 0000000..639a335 --- /dev/null +++ b/tikzit-1/src/gtk/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-1/src/gtk/tzstockitems.h b/tikzit-1/src/gtk/tzstockitems.h new file mode 100644 index 0000000..5ad0da9 --- /dev/null +++ b/tikzit-1/src/gtk/tzstockitems.h @@ -0,0 +1,26 @@ +/* + * Copyright 2012 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/>. + */ + +#define TIKZIT_STOCK_SELECT "tikzit-select" +#define TIKZIT_STOCK_CREATE_NODE "tikzit-create-node" +#define TIKZIT_STOCK_CREATE_EDGE "tikzit-create-edge" +#define TIKZIT_STOCK_BOUNDING_BOX "tikzit-bounding-box" +#define TIKZIT_STOCK_DRAG "tikzit-drag" + +void tz_register_stock_items(); + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/tzstockitems.m b/tikzit-1/src/gtk/tzstockitems.m new file mode 100644 index 0000000..5eba912 --- /dev/null +++ b/tikzit-1/src/gtk/tzstockitems.m @@ -0,0 +1,64 @@ +/* + * Copyright 2012 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/>. + */ + +#include "tzstockitems.h" +#include <gtk/gtk.h> +#include <gdk-pixbuf/gdk-pixdata.h> + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpointer-sign" +#import "icondata.m" +#pragma GCC diagnostic pop + +static GtkStockItem stock_items[] = { + // gchar *stock_id; + // gchar *label; + // GdkModifierType modifier; + // guint keyval; + // gchar *translation_domain; + { TIKZIT_STOCK_SELECT, "Select", 0, 0, NULL }, + { TIKZIT_STOCK_CREATE_NODE, "Create Node", 0, 0, NULL }, + { TIKZIT_STOCK_CREATE_EDGE, "Create Edge", 0, 0, NULL }, + { TIKZIT_STOCK_BOUNDING_BOX, "Bounding Box", 0, 0, NULL }, + { TIKZIT_STOCK_DRAG, "Drag", 0, 0, NULL }, +}; +static guint n_stock_items = G_N_ELEMENTS (stock_items); + +static void icon_factory_add_pixdata (GtkIconFactory *factory, + const gchar *stock_id, + const GdkPixdata *image_data) { + + GdkPixbuf *buf = gdk_pixbuf_from_pixdata (image_data, FALSE, NULL); + GtkIconSet *icon_set = gtk_icon_set_new_from_pixbuf (buf); + gtk_icon_factory_add (factory, stock_id, icon_set); + gtk_icon_set_unref (icon_set); + g_object_unref (G_OBJECT (buf)); +} + +void tz_register_stock_items() { + gtk_stock_add_static (stock_items, n_stock_items); + + GtkIconFactory *factory = gtk_icon_factory_new (); + icon_factory_add_pixdata (factory, TIKZIT_STOCK_SELECT, &select_rectangular); + icon_factory_add_pixdata (factory, TIKZIT_STOCK_CREATE_NODE, &draw_ellipse); + icon_factory_add_pixdata (factory, TIKZIT_STOCK_CREATE_EDGE, &draw_path); + icon_factory_add_pixdata (factory, TIKZIT_STOCK_BOUNDING_BOX, &transform_crop_and_resize); + icon_factory_add_pixdata (factory, TIKZIT_STOCK_DRAG, &transform_move); + gtk_icon_factory_add_default (factory); +} + +// vim:ft=objc:ts=8:et:sts=4:sw=4:foldmethod=marker diff --git a/tikzit-1/src/gtk/tztoolpalette.h b/tikzit-1/src/gtk/tztoolpalette.h new file mode 100644 index 0000000..45ec2ac --- /dev/null +++ b/tikzit-1/src/gtk/tztoolpalette.h @@ -0,0 +1,56 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * tztoolpalette.h, based on gimptoolpalette.h + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * + * 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 3 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/>. + */ + +#ifndef __TZ_TOOL_PALETTE_H__ +#define __TZ_TOOL_PALETTE_H__ + + +#define TZ_TYPE_TOOL_PALETTE (tz_tool_palette_get_type ()) +#define TZ_TOOL_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TZ_TYPE_TOOL_PALETTE, TzToolPalette)) +#define TZ_TOOL_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TZ_TYPE_TOOL_PALETTE, TzToolPaletteClass)) +#define TZ_IS_TOOL_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TZ_TYPE_TOOL_PALETTE)) +#define TZ_IS_TOOL_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TZ_TYPE_TOOL_PALETTE)) +#define TZ_TOOL_PALETTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TZ_TYPE_TOOL_PALETTE, TzToolPaletteClass)) + + +typedef struct _TzToolPaletteClass TzToolPaletteClass; +typedef struct _TzToolPalette TzToolPalette; + +struct _TzToolPalette +{ + GtkToolPalette parent_instance; +}; + +struct _TzToolPaletteClass +{ + GtkToolPaletteClass parent_class; +}; + + +GType tz_tool_palette_get_type (void) G_GNUC_CONST; + +GtkWidget * tz_tool_palette_new (void); + +gboolean tz_tool_palette_get_button_size (TzToolPalette *widget, + gint *width, + gint *height); + + +#endif /* __TZ_TOOL_PALETTE_H__ */ diff --git a/tikzit-1/src/gtk/tztoolpalette.m b/tikzit-1/src/gtk/tztoolpalette.m new file mode 100644 index 0000000..a948127 --- /dev/null +++ b/tikzit-1/src/gtk/tztoolpalette.m @@ -0,0 +1,158 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * tztoolpalette.c, based on gimptoolpalette.c + * Copyright (C) 2010 Michael Natterer <mitch@gimp.org> + * Copyright (C) 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 3 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 <gtk/gtk.h> + +#include "tztoolpalette.h" + + +#define DEFAULT_TOOL_ICON_SIZE GTK_ICON_SIZE_BUTTON +#define DEFAULT_BUTTON_RELIEF GTK_RELIEF_NONE + +#define TOOL_BUTTON_DATA_KEY "tz-tool-palette-item" +#define TOOL_INFO_DATA_KEY "tz-tool-info" + + +typedef struct _TzToolPalettePrivate TzToolPalettePrivate; + +struct _TzToolPalettePrivate +{ + gint tool_rows; + gint tool_columns; +}; + +#define GET_PRIVATE(p) G_TYPE_INSTANCE_GET_PRIVATE (p, \ + TZ_TYPE_TOOL_PALETTE, \ + TzToolPalettePrivate) + + +static void tz_tool_palette_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); + + +G_DEFINE_TYPE (TzToolPalette, tz_tool_palette, GTK_TYPE_TOOL_PALETTE) + +#define parent_class tz_tool_palette_parent_class + + +static void +tz_tool_palette_class_init (TzToolPaletteClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + widget_class->size_allocate = tz_tool_palette_size_allocate; + + g_type_class_add_private (klass, sizeof (TzToolPalettePrivate)); +} + +static void +tz_tool_palette_init (TzToolPalette *palette) +{ +} + +static GtkToolItemGroup * +tz_tool_palette_tool_group (TzToolPalette *palette) +{ + GList *children; + GtkToolItemGroup *group; + + children = gtk_container_get_children (GTK_CONTAINER (palette)); + g_return_val_if_fail (children, NULL); + group = GTK_TOOL_ITEM_GROUP (children->data); + g_list_free (children); + + return group; +} + +gboolean +tz_tool_palette_get_button_size (TzToolPalette *palette, + gint *width, + gint *height) +{ + g_return_val_if_fail (width || height, FALSE); + + GtkToolItemGroup *group = tz_tool_palette_tool_group (palette); + g_return_val_if_fail (group, FALSE); + + guint tool_count = gtk_tool_item_group_get_n_items (group); + if (tool_count > 0) + { + GtkWidget *tool_button; + GtkRequisition button_requisition; + + tool_button = GTK_WIDGET (gtk_tool_item_group_get_nth_item (group, 0)); + gtk_widget_size_request (tool_button, &button_requisition); + if (width) + *width = button_requisition.width; + if (height) + *height = button_requisition.height; + return TRUE; + } + return FALSE; +} + +static void +tz_tool_palette_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + TzToolPalettePrivate *private = GET_PRIVATE (widget); + GtkToolItemGroup *group = tz_tool_palette_tool_group (TZ_TOOL_PALETTE (widget)); + + g_return_if_fail (group); + + GTK_WIDGET_CLASS (parent_class)->size_allocate (widget, allocation); + + guint tool_count = gtk_tool_item_group_get_n_items (group); + if (tool_count > 0) + { + GtkWidget *tool_button; + GtkRequisition button_requisition; + gint tool_rows; + gint tool_columns; + + tool_button = GTK_WIDGET (gtk_tool_item_group_get_nth_item (group, 0)); + gtk_widget_size_request (tool_button, &button_requisition); + + tool_columns = MAX (1, (allocation->width / button_requisition.width)); + tool_rows = tool_count / tool_columns; + + if (tool_count % tool_columns) + tool_rows++; + + if (private->tool_rows != tool_rows || + private->tool_columns != tool_columns) + { + private->tool_rows = tool_rows; + private->tool_columns = tool_columns; + + gtk_widget_set_size_request (widget, -1, + tool_rows * button_requisition.height); + } + } +} + +GtkWidget * +tz_tool_palette_new (void) +{ + return g_object_new (TZ_TYPE_TOOL_PALETTE, NULL); +} + +// vim:ft=objc:ts=8:et:sts=2:sw=2:foldmethod=marker diff --git a/tikzit-1/src/osx/AppDelegate.h b/tikzit-1/src/osx/AppDelegate.h new file mode 100644 index 0000000..92d9add --- /dev/null +++ b/tikzit-1/src/osx/AppDelegate.h @@ -0,0 +1,57 @@ +// +// 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" +#import "PreferenceController.h" + +@interface AppDelegate : NSObject { + NSMapTable *table; + StylePaletteController *stylePaletteController; + PropertyInspectorController *propertyInspectorController; + PreambleController *preambleController; + PreviewController *previewController; + PreferenceController *preferenceController; + ToolPaletteController *toolPaletteController; + IBOutlet GraphicsView *graphicsView; + NSString *tempDir; +} + +@property IBOutlet StylePaletteController *stylePaletteController; +@property (strong) IBOutlet ToolPaletteController *toolPaletteController; + +- (void)awakeFromNib; ++ (void)setDefaults; +- (void)applicationWillTerminate:(NSNotification *)notification; +- (IBAction)toggleStyleInspector:(id)sender; +- (IBAction)togglePropertyInspector:(id)sender; +- (IBAction)togglePreamble:(id)sender; +- (IBAction)togglePreferences:(id)sender; +- (IBAction)refreshShapes:(id)sender; + +@end diff --git a/tikzit-1/src/osx/AppDelegate.m b/tikzit-1/src/osx/AppDelegate.m new file mode 100644 index 0000000..94f5507 --- /dev/null +++ b/tikzit-1/src/osx/AppDelegate.m @@ -0,0 +1,124 @@ +// +// 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 "TikzDocument.h" +#import "Shape.h" +#import "SupportDir.h" + +@implementation AppDelegate + +@synthesize stylePaletteController, toolPaletteController; + ++(void)initialize{ + [self setDefaults]; +} + +- (void)awakeFromNib { + [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] initWithNibName:@"Preamble" + plist:preamblePlist + styles:[stylePaletteController nodeStyles] + edges:[stylePaletteController edgeStyles]]; + + + 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]; + + preferenceController = [[PreferenceController alloc] initWithWindowNibName:@"Preferences" preambleController:preambleController]; + + // each application has one global preview controller + [PreviewController setDefaultPreviewController:previewController]; +} + ++ (void)setDefaults{ + NSString *userDefaultsValuesPath; + NSDictionary *userDefaultsValuesDict; + + userDefaultsValuesPath=[[NSBundle mainBundle] pathForResource:@"UserDefaults" + ofType:@"plist"]; + userDefaultsValuesDict=[NSDictionary dictionaryWithContentsOfFile:userDefaultsValuesPath]; + + [[NSUserDefaults standardUserDefaults] registerDefaults:userDefaultsValuesDict]; +} + +- (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:(NSWindowController *) preambleController]; +} + +- (IBAction)togglePreferences:(id)sender { + [self toggleController:preferenceController]; +} + +- (IBAction)refreshShapes:(id)sender { + [Shape refreshShapeDictionary]; +} + +@end diff --git a/tikzit-1/src/osx/CALayer+DrawLabel.h b/tikzit-1/src/osx/CALayer+DrawLabel.h new file mode 100644 index 0000000..32282d9 --- /dev/null +++ b/tikzit-1/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-1/src/osx/CALayer+DrawLabel.m b/tikzit-1/src/osx/CALayer+DrawLabel.m new file mode 100644 index 0000000..4860a3c --- /dev/null +++ b/tikzit-1/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-1/src/osx/CoreGraphicsRenderContext.h b/tikzit-1/src/osx/CoreGraphicsRenderContext.h new file mode 100644 index 0000000..7b00484 --- /dev/null +++ b/tikzit-1/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-1/src/osx/CoreGraphicsRenderContext.m b/tikzit-1/src/osx/CoreGraphicsRenderContext.m new file mode 100644 index 0000000..1cb0daf --- /dev/null +++ b/tikzit-1/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-1/src/osx/CustomNodeCellView.h b/tikzit-1/src/osx/CustomNodeCellView.h new file mode 100644 index 0000000..22606d7 --- /dev/null +++ b/tikzit-1/src/osx/CustomNodeCellView.h @@ -0,0 +1,23 @@ +// +// CustomNodeCellView.h +// TikZiT +// +// Created by Johan Paulsson on 12/12/13. +// Copyright (c) 2013 Aleks Kissinger. All rights reserved. +// + +#import <Cocoa/Cocoa.h> + +#import "NodeLayer.h" +#import "NodeStyle.h" +#import "NodeStyle+Coder.h" + +@interface CustomNodeCellView : NSTableCellView{ + NodeLayer *nodeLayer; + NodeStyle *nodeStyle; + BOOL selected; +} + +@property (strong) id objectValue; + +@end diff --git a/tikzit-1/src/osx/CustomNodeCellView.m b/tikzit-1/src/osx/CustomNodeCellView.m new file mode 100644 index 0000000..612394b --- /dev/null +++ b/tikzit-1/src/osx/CustomNodeCellView.m @@ -0,0 +1,83 @@ +// +// CustomNodeCellView.m +// TikZiT +// +// Created by Johan Paulsson on 12/12/13. +// Copyright (c) 2013 Aleks Kissinger. All rights reserved. +// + +#import "CustomNodeCellView.h" + +@implementation CustomNodeCellView + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + // Initialization code here. + } + return self; +} + +- (void)drawRect:(NSRect)dirtyRect +{ + [super drawRect:dirtyRect]; + + // Drawing code here. +} + +- (id) objectValue{ + return [super objectValue]; +} + +-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context { + if (nodeLayer!=nil) { + if (![[[self layer] sublayers] containsObject:nodeLayer]) { + [[self layer] addSublayer:nodeLayer]; + NSPoint c = NSMakePoint((CGRectGetMinX([[self layer] frame])+CGRectGetWidth([nodeLayer bounds])/2), + CGRectGetMidY([[self layer] frame])); + //c = NSMakePoint(-16.5,-16.5); + [nodeLayer setCenter:c andAnimateWhen:NO]; + [[self textField] setFrame:NSMakeRect(CGRectGetWidth([nodeLayer bounds]), CGRectGetMidY([[self layer] frame]), CGRectGetWidth([[self textField] frame]), CGRectGetHeight([[self textField] frame]))]; + } + + if (selected){ + [nodeStyle setStrokeColor:[NSColor whiteColor]]; + [[nodeLayer node] setStyle:nodeStyle]; + }else{ + [nodeStyle setStrokeColor:[NSColor blackColor]]; + [[nodeLayer node] setStyle:nodeStyle]; + } + + [nodeLayer updateFrame]; + } +} + +- (void) setObjectValue:(id)objectValue{ + if(objectValue == nil) + return; + + nodeStyle = (NodeStyle *)objectValue; + [[self textField] setStringValue:[nodeStyle shapeName]]; + + if (nodeLayer == nil) { + nodeLayer = [[NodeLayer alloc] initWithNode:[Node node] + transformer:[Transformer defaultTransformer]]; + [nodeLayer setRescale:NO]; + } + [nodeStyle setName:[nodeStyle shapeName]]; + + [[nodeLayer node] setStyle:nodeStyle]; + [nodeLayer updateFrame]; + + NSLog(@"asd"); +} + +- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle { + [super setBackgroundStyle:backgroundStyle]; + + selected = (backgroundStyle == NSBackgroundStyleDark); + [self setNeedsDisplay:YES]; +} + +@end diff --git a/tikzit-1/src/osx/CustomNodeController.h b/tikzit-1/src/osx/CustomNodeController.h new file mode 100644 index 0000000..67adf0b --- /dev/null +++ b/tikzit-1/src/osx/CustomNodeController.h @@ -0,0 +1,35 @@ +// +// CustomNodeController.h +// TikZiT +// +// Created by Johan Paulsson on 12/4/13. +// Copyright (c) 2013 Aleks Kissinger. All rights reserved. +// + +#import <Cocoa/Cocoa.h> +#import "Shape.h" +#import "TikzShape.h" + +#import "GraphicsView.h" +#import "TikzSourceController.h" + +#import "SupportDir.h" + +@interface CustomNodeController : NSViewController <NSTableViewDelegate>{ + NSDictionary *nodeStyles; + NSMutableArray* customNodeStyles; + + GraphicsView *__weak graphicsView; + TikzSourceController *__weak tikzSourceController; + NSTableView *customNodeTable; +} + +@property NSDictionary *nodeStyles; +@property NSMutableArray* customNodeStyles; + +@property IBOutlet NSTableView *customNodeTable; + +@property (weak) IBOutlet GraphicsView *graphicsView; +@property (weak) IBOutlet TikzSourceController *tikzSourceController; + +@end diff --git a/tikzit-1/src/osx/CustomNodeController.m b/tikzit-1/src/osx/CustomNodeController.m new file mode 100644 index 0000000..4f46acc --- /dev/null +++ b/tikzit-1/src/osx/CustomNodeController.m @@ -0,0 +1,58 @@ +// +// CustomNodeController.m +// TikZiT +// +// Created by Johan Paulsson on 12/4/13. +// Copyright (c) 2013 Aleks Kissinger. All rights reserved. +// + +#import "CustomNodeController.h" +#import "NodeStyle.h" + +@interface CustomNodeController () + +@end + +@implementation CustomNodeController + +@synthesize nodeStyles, customNodeStyles; +@synthesize graphicsView, tikzSourceController; +@synthesize customNodeTable; + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { + nodeStyles = [Shape shapeDictionary]; + customNodeStyles = [NSMutableArray array]; + + for(id key in nodeStyles) { + Shape *value = [nodeStyles objectForKey:key]; + + if([value isKindOfClass:[TikzShape class]]){ + NodeStyle *newNodeStyle = [[NodeStyle alloc] init]; + [newNodeStyle setShapeName:key]; + + [customNodeStyles addObject:newNodeStyle]; + } + } + } + + return self; +} + +- (void)tableViewSelectionDidChange:(NSNotification *)aNotification{ + NSInteger selectedRow = [customNodeTable selectedRow]; + + NodeStyle* selectedNodeStyle = [customNodeStyles objectAtIndex:selectedRow]; + TikzShape *tikzshape = (TikzShape *) [nodeStyles objectForKey:[selectedNodeStyle shapeName]]; + + [[tikzSourceController graphicsView] setEnabled:NO]; + [tikzSourceController setTikz:[tikzshape tikzSrc]]; + [tikzSourceController parseTikz:self]; +} + +- (id)valueForUndefinedKey:(NSString *)key{ + return nil; +} + +@end diff --git a/tikzit-1/src/osx/CustomNodes.xib b/tikzit-1/src/osx/CustomNodes.xib new file mode 100644 index 0000000..1cc8db2 --- /dev/null +++ b/tikzit-1/src/osx/CustomNodes.xib @@ -0,0 +1,249 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="5053" systemVersion="13B42" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> + <dependencies> + <deployment defaultVersion="1070" identifier="macosx"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="5053"/> + </dependencies> + <objects> + <customObject id="-2" userLabel="File's Owner" customClass="CustomNodeController"> + <connections> + <outlet property="graphicsView" destination="3MT-Yc-Dhv" id="9gg-fS-oBl"/> + <outlet property="tikzSourceController" destination="il5-cQ-3oh" id="Wny-jd-sKZ"/> + <outlet property="view" destination="1" id="sH5-DU-xwB"/> + </connections> + </customObject> + <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> + <customObject id="-3" userLabel="Application"/> + <arrayController objectClassName="NodeStyle" id="iyU-U5-XAU" userLabel="Node Style Array Controller"> + <declaredKeys> + <string>name</string> + <string>strokeThickness</string> + <string>strokeColor</string> + <string>fillColor</string> + <string>strokeColorIsKnown</string> + <string>fillColorIsKnown</string> + <string>representedObject.name</string> + <string>shapeName</string> + <string>scale</string> + <string>@distinctUnionOfObjects.category</string> + <string>category</string> + </declaredKeys> + <connections> + <binding destination="-2" name="contentArray" keyPath="onodeStyles" id="Yzy-xv-qEa"/> + </connections> + </arrayController> + <customObject id="il5-cQ-3oh" customClass="TikzSourceController"> + <connections> + <outlet property="errorMessage" destination="6rJ-4b-syy" id="cin-Wb-6uD"/> + <outlet property="errorNotification" destination="R3k-mz-hMn" id="jtn-fQ-Amk"/> + <outlet property="graphicsView" destination="3MT-Yc-Dhv" id="hK1-h1-Dsr"/> + <outlet property="sourceView" destination="QXu-0d-uF0" id="SEj-FL-5Ac"/> + <outlet property="status" destination="6rJ-4b-syy" id="lVN-cK-3Cb"/> + </connections> + </customObject> + <customView id="1"> + <rect key="frame" x="0.0" y="0.0" width="480" height="272"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <subviews> + <splitView dividerStyle="thin" vertical="YES" translatesAutoresizingMaskIntoConstraints="NO" id="DO1-FS-fZd"> + <rect key="frame" x="0.0" y="0.0" width="480" height="272"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <subviews> + <scrollView fixedFrame="YES" autohidesScrollers="YES" horizontalLineScroll="32" horizontalPageScroll="10" verticalLineScroll="32" verticalPageScroll="10" usesPredominantAxisScrolling="NO" id="Ry0-48-577"> + <rect key="frame" x="0.0" y="0.0" width="117" height="272"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <clipView key="contentView" id="9D3-br-lEi"> + <rect key="frame" x="1" y="1" width="115" height="270"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" columnReordering="NO" columnSelection="YES" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" rowHeight="30" rowSizeStyle="automatic" viewBased="YES" id="ieg-vU-MzM"> + <rect key="frame" x="0.0" y="0.0" width="115" height="270"/> + <autoresizingMask key="autoresizingMask"/> + <size key="intercellSpacing" width="3" height="2"/> + <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> + <color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/> + <tableColumns> + <tableColumn width="112" minWidth="40" maxWidth="1000" id="k91-pc-nbl"> + <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left"> + <font key="font" metaFont="smallSystem"/> + <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" white="0.33333298560000002" alpha="1" colorSpace="calibratedWhite"/> + </tableHeaderCell> + <textFieldCell key="dataCell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="ZaR-9P-sAp"> + <font key="font" metaFont="system"/> + <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + <tableColumnResizingMask key="resizingMask" resizeWithTable="YES" userResizable="YES"/> + <prototypeCellViews> + <tableCellView id="0ce-nA-hcl" customClass="CustomNodeCellView"> + <rect key="frame" x="1" y="1" width="112" height="30"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KUl-Qh-CHH"> + <rect key="frame" x="36" y="3" width="87" height="17"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> + <textFieldCell key="cell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" title="Table View Cell" id="92K-au-Qpa"> + <font key="font" metaFont="system"/> + <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> + <connections> + <binding destination="0ce-nA-hcl" name="value" keyPath="objectValue" id="06y-KE-8wP"/> + </connections> + </textFieldCell> + </textField> + </subviews> + <connections> + <outlet property="textField" destination="KUl-Qh-CHH" id="Cze-5m-UmF"/> + </connections> + </tableCellView> + </prototypeCellViews> + </tableColumn> + </tableColumns> + <connections> + <binding destination="-2" name="selectionIndexes" keyPath="selectedNode" id="C4J-62-GDG"/> + <outlet property="delegate" destination="-2" id="M05-fU-pjz"/> + </connections> + </tableView> + </subviews> + <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> + </clipView> + <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="QIL-7j-JmC"> + <rect key="frame" x="1" y="256" width="0.0" height="15"/> + <autoresizingMask key="autoresizingMask"/> + </scroller> + <scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="ElP-ub-Uwa"> + <rect key="frame" x="224" y="17" width="15" height="102"/> + <autoresizingMask key="autoresizingMask"/> + </scroller> + </scrollView> + <view fixedFrame="YES" id="F5P-T2-9eB"> + <rect key="frame" x="118" y="0.0" width="362" height="272"/> + <autoresizingMask key="autoresizingMask"/> + <subviews> + <splitView fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KKi-SZ-dS6"> + <rect key="frame" x="0.0" y="0.0" width="362" height="272"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <customView fixedFrame="YES" id="d61-FR-v2c"> + <rect key="frame" x="0.0" y="0.0" width="362" height="156"/> + <autoresizingMask key="autoresizingMask"/> + <subviews> + <customView wantsLayer="YES" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="3MT-Yc-Dhv" customClass="GraphicsView"> + <rect key="frame" x="0.0" y="0.0" width="362" height="155"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + </customView> + <customView wantsLayer="YES" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="R3k-mz-hMn" customClass="ParseErrorView"> + <rect key="frame" x="0.0" y="113" width="361" height="42"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/> + <subviews> + <textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="6rJ-4b-syy"> + <rect key="frame" x="5" y="10" width="354" height="27"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" flexibleMinY="YES"/> + <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="left" placeholderString="parser information" allowsEditingTextAttributes="YES" id="3y1-Zn-Hv4"> + <font key="font" metaFont="smallSystem"/> + <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + <button wantsLayer="YES" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Et8-Cc-Xac"> + <rect key="frame" x="340" y="21" width="17" height="19"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="squareTextured" bezelStyle="texturedSquare" image="NSStopProgressFreestandingTemplate" imagePosition="only" alignment="center" state="on" imageScaling="proportionallyDown" id="7vB-Le-tP4"> + <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="closeParseError:" target="il5-cQ-3oh" id="jdZ-7K-cwe"/> + </connections> + </button> + </subviews> + </customView> + </subviews> + </customView> + <customView fixedFrame="YES" id="Fal-9S-dwR"> + <rect key="frame" x="0.0" y="165" width="362" height="107"/> + <autoresizingMask key="autoresizingMask"/> + <subviews> + <scrollView fixedFrame="YES" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yLp-gf-r49"> + <rect key="frame" x="0.0" y="0.0" width="362" height="107"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <clipView key="contentView" ambiguous="YES" misplaced="YES" id="qOT-id-lqk"> + <rect key="frame" x="1" y="1" width="345" height="105"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <subviews> + <textView ambiguous="YES" importsGraphics="NO" richText="NO" findStyle="panel" allowsUndo="YES" usesRuler="YES" verticallyResizable="YES" allowsNonContiguousLayout="YES" spellingCorrection="YES" smartInsertDelete="YES" id="QXu-0d-uF0"> + <rect key="frame" x="0.0" y="0.0" width="345" height="105"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> + <size key="minSize" width="345" height="105"/> + <size key="maxSize" width="877" height="10000000"/> + <attributedString key="textStorage"> + <fragment> + <string key="content">\begin{tikzpicture} + +\end{tikzpicture}</string> + <attributes> + <font key="NSFont" metaFont="toolTip"/> + <paragraphStyle key="NSParagraphStyle" alignment="natural" lineBreakMode="wordWrapping" baseWritingDirection="natural"/> + </attributes> + </fragment> + </attributedString> + <color key="insertionPointColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/> + <size key="minSize" width="345" height="105"/> + <size key="maxSize" width="877" height="10000000"/> + <connections> + <binding destination="il5-cQ-3oh" name="attributedString" keyPath="source" id="15z-o3-4ni"/> + <outlet property="delegate" destination="il5-cQ-3oh" id="9Mr-J3-zvR"/> + </connections> + </textView> + </subviews> + <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> + </clipView> + <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="BYM-Xm-JpT"> + <rect key="frame" x="-100" y="-100" width="87" height="18"/> + <autoresizingMask key="autoresizingMask"/> + </scroller> + <scroller key="verticalScroller" verticalHuggingPriority="750" doubleValue="0.90909090909090906" horizontal="NO" id="SEU-Jq-fwu"> + <rect key="frame" x="346" y="1" width="15" height="105"/> + <autoresizingMask key="autoresizingMask"/> + </scroller> + </scrollView> + </subviews> + </customView> + </subviews> + <holdingPriorities> + <real value="250"/> + <real value="250"/> + </holdingPriorities> + </splitView> + <textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="esG-a3-KD7"> + <rect key="frame" x="220" y="-184" width="128" height="14"/> + <autoresizingMask key="autoresizingMask" flexibleMinX="YES" flexibleMaxY="YES"/> + <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" id="7uu-y8-NcI"> + <font key="font" metaFont="smallSystem"/> + <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> + </textFieldCell> + </textField> + </subviews> + </view> + </subviews> + <holdingPriorities> + <real value="250"/> + <real value="250"/> + </holdingPriorities> + </splitView> + </subviews> + <constraints> + <constraint firstAttribute="trailing" secondItem="DO1-FS-fZd" secondAttribute="trailing" id="Bow-1P-jx4"/> + <constraint firstAttribute="bottom" secondItem="DO1-FS-fZd" secondAttribute="bottom" id="c94-eC-Rhp"/> + <constraint firstItem="DO1-FS-fZd" firstAttribute="leading" secondItem="1" secondAttribute="leading" id="j1G-Ag-RLs"/> + <constraint firstItem="DO1-FS-fZd" firstAttribute="top" secondItem="1" secondAttribute="top" id="tBr-89-Ipp"/> + </constraints> + </customView> + </objects> + <resources> + <image name="NSStopProgressFreestandingTemplate" width="12" height="12"/> + </resources> +</document> diff --git a/tikzit-1/src/osx/DraggablePDFView.h b/tikzit-1/src/osx/DraggablePDFView.h new file mode 100644 index 0000000..9e53c44 --- /dev/null +++ b/tikzit-1/src/osx/DraggablePDFView.h @@ -0,0 +1,28 @@ +// +// 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 <Quartz/Quartz.h> + +@interface DraggablePDFView : PDFView + +@end diff --git a/tikzit-1/src/osx/DraggablePDFView.m b/tikzit-1/src/osx/DraggablePDFView.m new file mode 100644 index 0000000..ce393c7 --- /dev/null +++ b/tikzit-1/src/osx/DraggablePDFView.m @@ -0,0 +1,60 @@ +// +// 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 "DraggablePDFView.h" + +@implementation DraggablePDFView + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + return self; +} + +- (void)drawRect:(NSRect)dirtyRect +{ + [super drawRect:dirtyRect]; +} + +- (void)mouseDown:(NSEvent *)theEvent +{ + NSRect pageBox = [[[self document] pageAtIndex:0] boundsForBox:kPDFDisplayBoxMediaBox]; + NSRect pageRect= [self convertRect:pageBox fromPage:[[self document] pageAtIndex:0]]; + + NSArray *fileList = [NSArray arrayWithObjects:[[[self document] documentURL] path], nil]; + NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard]; + [pboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil]; + [pboard setPropertyList:fileList forType:NSFilenamesPboardType]; + + [self dragImage:[[NSImage alloc] initWithData:[[self document] dataRepresentation]] + at:pageRect.origin + offset:pageRect.size + event:theEvent + pasteboard:pboard + source:self + slideBack:YES]; + + return; +} + +@end diff --git a/tikzit-1/src/osx/EdgeControlLayer.h b/tikzit-1/src/osx/EdgeControlLayer.h new file mode 100644 index 0000000..4cdf8bc --- /dev/null +++ b/tikzit-1/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-1/src/osx/EdgeControlLayer.m b/tikzit-1/src/osx/EdgeControlLayer.m new file mode 100644 index 0000000..facdd84 --- /dev/null +++ b/tikzit-1/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 { + if (!(self = [super init])) return nil; + 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-1/src/osx/EdgeStyle+Coder.h b/tikzit-1/src/osx/EdgeStyle+Coder.h new file mode 100644 index 0000000..e35c18f --- /dev/null +++ b/tikzit-1/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-1/src/osx/EdgeStyle+Coder.m b/tikzit-1/src/osx/EdgeStyle+Coder.m new file mode 100644 index 0000000..039344d --- /dev/null +++ b/tikzit-1/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 { + if (!(self = [super init])) return nil; + + 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-1/src/osx/Graph+Coder.h b/tikzit-1/src/osx/Graph+Coder.h new file mode 100644 index 0000000..1404fc2 --- /dev/null +++ b/tikzit-1/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-1/src/osx/Graph+Coder.m b/tikzit-1/src/osx/Graph+Coder.m new file mode 100644 index 0000000..7d3787e --- /dev/null +++ b/tikzit-1/src/osx/Graph+Coder.m @@ -0,0 +1,24 @@ +// +// 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 parseTikz:tikz forGraph:self]; + return self; +} + +- (void)encodeWithCoder:(NSCoder*)coder { + [coder encodeObject:[self tikz]]; +} + +@end diff --git a/tikzit-1/src/osx/GraphicsView.h b/tikzit-1/src/osx/GraphicsView.h new file mode 100644 index 0000000..329b1e5 --- /dev/null +++ b/tikzit-1/src/osx/GraphicsView.h @@ -0,0 +1,129 @@ +// +// 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 (weak) 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)bringForward:(id)sender; +- (void)flipHorizonal:(id)sender; +- (void)flipVertical:(id)sender; +- (void)reverseEdgeDirection:(id)sender; + +@end diff --git a/tikzit-1/src/osx/GraphicsView.m b/tikzit-1/src/osx/GraphicsView.m new file mode 100644 index 0000000..efa7ecb --- /dev/null +++ b/tikzit-1/src/osx/GraphicsView.m @@ -0,0 +1,1216 @@ +// +// 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 tail]]; + NSPoint targ = [transformer toScreen:[edge head]]; + 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 tail]]; + NSPoint targ = [transformer toScreen:[e head]]; + 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/tdist * tradius; + float tshorty = (tdist==0) ? 0 : tdy/tdist * 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); + + + float lineWidth = [transformer scaleToScreen:0.04f]; + + CGContextSetLineWidth(context, lineWidth); + CGContextSetRGBStrokeColor(context, 0, 0, 0, 1); + CGContextSetRGBFillColor(context, 0, 0, 0, 1); + CGContextStrokePath(context); + + 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); + CGContextStrokePath(context); + 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); + CGContextStrokePath(context); + break; + } + + // draw arrow head + switch ([[e style] headStyle]) { + case AH_None: + break; + case AH_Plain: + p1 = [transformer toScreen:[e leftHeadNormal]]; + p2 = [transformer toScreen:[e head]]; + p3 = [transformer toScreen:[e rightHeadNormal]]; + CGContextMoveToPoint(context, p1.x, p1.y); + CGContextAddLineToPoint(context, p2.x, p2.y); + CGContextAddLineToPoint(context, p3.x, p3.y); + CGContextStrokePath(context); + break; + case AH_Latex: + p1 = [transformer toScreen:[e leftHeadNormal]]; + p2 = [transformer toScreen:[e head]]; + p3 = [transformer toScreen:[e rightHeadNormal]]; + CGContextMoveToPoint(context, p1.x, p1.y); + CGContextAddLineToPoint(context, p2.x, p2.y); + CGContextAddLineToPoint(context, p3.x, p3.y); + CGContextClosePath(context); + CGContextFillPath(context); + break; + } + + // draw arrow tail + switch ([[e style] tailStyle]) { + case AH_None: + break; + case AH_Plain: + p1 = [transformer toScreen:[e leftTailNormal]]; + p2 = [transformer toScreen:[e tail]]; + p3 = [transformer toScreen:[e rightTailNormal]]; + CGContextMoveToPoint(context, p1.x, p1.y); + CGContextAddLineToPoint(context, p2.x, p2.y); + CGContextAddLineToPoint(context, p3.x, p3.y); + CGContextStrokePath(context); + break; + case AH_Latex: + p1 = [transformer toScreen:[e leftTailNormal]]; + p2 = [transformer toScreen:[e tail]]; + p3 = [transformer toScreen:[e rightTailNormal]]; + CGContextMoveToPoint(context, p1.x, p1.y); + CGContextAddLineToPoint(context, p2.x, p2.y); + CGContextAddLineToPoint(context, p3.x, p3.y); + CGContextClosePath(context); + CGContextFillPath(context); + break; + } + } + + + 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:[NSSet setWithArray:[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); + Graph *clip = [TikzGraphAssembler parseTikz:tikz]; + if (clip) { + //NSLog(@"tikz pasted:\n%@",tikz); + 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)bringForward:(id)sender { + NSString *oldTikz = [graph tikz]; + [graph bringNodesForward:[pickSupport selectedNodes]]; + [graph bringEdgesForward:[pickSupport selectedEdges]]; + [self registerUndo:oldTikz withActionName:@"Bring Forward"]; + [self postGraphChange]; + [self refreshLayers]; +} + +- (void)sendBackward:(id)sender { + NSString *oldTikz = [graph tikz]; + [graph sendNodesBackward:[pickSupport selectedNodes]]; + [graph sendEdgesBackward:[pickSupport selectedEdges]]; + [self registerUndo:oldTikz withActionName:@"Send Backward"]; + [self postGraphChange]; + [self refreshLayers]; +} + +- (void)bringToFront:(id)sender { + NSString *oldTikz = [graph tikz]; + [graph bringNodesToFront:[pickSupport selectedNodes]]; + [graph bringEdgesToFront:[pickSupport selectedEdges]]; + [self registerUndo:oldTikz withActionName:@"Bring to Front"]; + [self postGraphChange]; + [self refreshLayers]; +} + +- (void)sendToBack:(id)sender { + NSString *oldTikz = [graph tikz]; + [graph sendNodesToBack:[pickSupport selectedNodes]]; + [graph sendEdgesToBack:[pickSupport selectedEdges]]; + [self registerUndo:oldTikz withActionName:@"Send to Back"]; + [self postGraphChange]; + [self refreshLayers]; +} + +- (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-1/src/osx/Grid.h b/tikzit-1/src/osx/Grid.h new file mode 100644 index 0000000..76826e2 --- /dev/null +++ b/tikzit-1/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-1/src/osx/Grid.m b/tikzit-1/src/osx/Grid.m new file mode 100644 index 0000000..aa35c1f --- /dev/null +++ b/tikzit-1/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 +{ + if (!(self = [super init])) return nil; + 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-1/src/osx/MultiCombo.h b/tikzit-1/src/osx/MultiCombo.h new file mode 100644 index 0000000..c8ec769 --- /dev/null +++ b/tikzit-1/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-1/src/osx/MultiCombo.m b/tikzit-1/src/osx/MultiCombo.m new file mode 100644 index 0000000..8930460 --- /dev/null +++ b/tikzit-1/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-1/src/osx/MultiField.h b/tikzit-1/src/osx/MultiField.h new file mode 100644 index 0000000..39eeefa --- /dev/null +++ b/tikzit-1/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-1/src/osx/MultiField.m b/tikzit-1/src/osx/MultiField.m new file mode 100644 index 0000000..7c5aac3 --- /dev/null +++ b/tikzit-1/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-1/src/osx/NilToEmptyStringTransformer.h b/tikzit-1/src/osx/NilToEmptyStringTransformer.h new file mode 100644 index 0000000..1445a94 --- /dev/null +++ b/tikzit-1/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-1/src/osx/NilToEmptyStringTransformer.m b/tikzit-1/src/osx/NilToEmptyStringTransformer.m new file mode 100644 index 0000000..413f404 --- /dev/null +++ b/tikzit-1/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 { + if (!(self = [super init])) return nil; + 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-1/src/osx/NodeLayer.h b/tikzit-1/src/osx/NodeLayer.h new file mode 100644 index 0000000..dbcdac5 --- /dev/null +++ b/tikzit-1/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 (strong) Node *node; +@property (assign) NSPoint center; +@property (assign) BOOL rescale; +@property (strong) 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-1/src/osx/NodeLayer.m b/tikzit-1/src/osx/NodeLayer.m new file mode 100644 index 0000000..5d15585 --- /dev/null +++ b/tikzit-1/src/osx/NodeLayer.m @@ -0,0 +1,238 @@ +// +// 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 { + if (!(self = [super init])) return nil; + 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 [NodeStyle defaultStrokeThickness]; + } +} + +- (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); +} + +@end diff --git a/tikzit-1/src/osx/NodeSelectionLayer.h b/tikzit-1/src/osx/NodeSelectionLayer.h new file mode 100644 index 0000000..99ee75f --- /dev/null +++ b/tikzit-1/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-1/src/osx/NodeSelectionLayer.m b/tikzit-1/src/osx/NodeSelectionLayer.m new file mode 100644 index 0000000..02b8ac2 --- /dev/null +++ b/tikzit-1/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 { + if (!(self = [super init])) return nil; + 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-1/src/osx/NodeStyle+Coder.h b/tikzit-1/src/osx/NodeStyle+Coder.h new file mode 100644 index 0000000..b6443af --- /dev/null +++ b/tikzit-1/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-1/src/osx/NodeStyle+Coder.m b/tikzit-1/src/osx/NodeStyle+Coder.m new file mode 100644 index 0000000..d3623f5 --- /dev/null +++ b/tikzit-1/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 { + if (!(self = [super init])) return nil; + + // 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-1/src/osx/ParseErrorView.h b/tikzit-1/src/osx/ParseErrorView.h new file mode 100644 index 0000000..bb6141f --- /dev/null +++ b/tikzit-1/src/osx/ParseErrorView.h @@ -0,0 +1,13 @@ +// +// ParseErrorView.h +// TikZiT +// +// Created by Karl Johan Paulsson on 27/01/2013. +// Copyright (c) 2013 Aleks Kissinger. All rights reserved. +// + +#import <Cocoa/Cocoa.h> + +@interface ParseErrorView : NSView + +@end diff --git a/tikzit-1/src/osx/ParseErrorView.m b/tikzit-1/src/osx/ParseErrorView.m new file mode 100644 index 0000000..83383f0 --- /dev/null +++ b/tikzit-1/src/osx/ParseErrorView.m @@ -0,0 +1,40 @@ +// +// ParseErrorView.m +// TikZiT +// +// Created by Karl Johan Paulsson on 27/01/2013. +// Copyright (c) 2013 Aleks Kissinger. All rights reserved. +// + +#import "ParseErrorView.h" + +@implementation ParseErrorView + +- (id)initWithFrame:(NSRect)frame +{ + self = [super initWithFrame:frame]; + if (self) { + // Initialization code here. + } + + return self; +} + +- (void)drawRect:(NSRect)dirtyRect +{ + // Drawing code here. +} + +- (void)awakeFromNib{ + self.layer = [CALayer layer]; + self.wantsLayer = YES; + CALayer *newLayer = [CALayer layer]; + self.layer.backgroundColor = [[NSColor controlColor] CGColor]; + //CGColorCreate(CGColorSpaceCreateDeviceRGB(), (CGFloat[]){ 1, .9, .64, 1 }); +// newLayer.backgroundColor = [NSColor redColor].CGColor; + newLayer.frame = CGRectMake(100,100,100,100);//NSMakeRect(0,0,image.size.width,image.size.height); + newLayer.position = CGPointMake(20,20); + //[self.layer addSublayer:newLayer]; +} + +@end diff --git a/tikzit-1/src/osx/PreambleController.h b/tikzit-1/src/osx/PreambleController.h new file mode 100644 index 0000000..5b0931d --- /dev/null +++ b/tikzit-1/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 : NSViewController { + Preambles *preambles; + IBOutlet NSTextView *textView; + IBOutlet NSDictionaryController *preambleDictionaryController; + + NSDictionary *textAttrs; + NSAttributedString *preambleText; + NSColor *ghostColor; + NSIndexSet *selectionIndexes; +} + +@property (readonly) BOOL useDefaultPreamble; +@property (readonly) Preambles *preambles; +@property (strong) NSAttributedString *preambleText; +@property (strong) NSIndexSet *selectionIndexes; + +- (id)initWithNibName:(NSString *)nibName plist:(NSString*)plist styles:(NSArray*)sty edges:(NSArray*)edg; +- (void)savePreambles:(NSString*)plist; +- (NSString*)currentPreamble; +- (NSString*)currentPostamble; +- (NSString*)buildDocumentForTikz:(NSString*)tikz; + +- (IBAction)setPreambleToDefault:(id)sender; +- (IBAction)setPreamble:(id)sender; +- (IBAction)insertDefaultStyles:(id)sender; + +- (IBAction)addPreamble:(id)sender; +- (IBAction)duplicatePreamble:(id)sender; + +@end diff --git a/tikzit-1/src/osx/PreambleController.m b/tikzit-1/src/osx/PreambleController.m new file mode 100644 index 0000000..206bb30 --- /dev/null +++ b/tikzit-1/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)initWithNibName:(NSString *)nibName plist:(NSString*)plist styles:(NSArray*)sty edges:(NSArray*)edg { + if (!(self = [super initWithNibName:nibName bundle:Nil])) return nil; + + preambles = (Preambles*)[NSKeyedUnarchiver unarchiveObjectWithFile:plist]; + [preambles setStyles:sty]; + [preambles setEdges:edg]; + 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)savePreambles:(NSString*)plist { + [self flushText]; + [NSKeyedArchiver archiveRootObject:preambles toFile:plist]; +} + +- (NSString*)currentPreamble { + [self flushText]; + return [preambles currentPreamble]; +} + +- (NSString*)currentPostamble { + return [preambles currentPostamble]; +} + +- (NSString*)buildDocumentForTikz:(NSString*)tikz { + [self flushText]; + return [preambles buildDocumentForTikz:tikz]; +} + +- (void)setSelectionIndexes:(NSIndexSet *)idx { + [self willChangeValueForKey:@"selectionIndexes"]; + selectionIndexes = idx; + [self didChangeValueForKey:@"selectionIndexes"]; + + [self setPreamble:self]; +} + +- (NSIndexSet*)selectionIndexes { + return selectionIndexes; +} + +- (IBAction)setPreambleToDefault:(id)sender{ + [self setCurrentPreamble:@"default"]; + [textView setBackgroundColor:ghostColor]; +} + +- (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:@"custom"]; + //NSLog(@"preamble set to custom"); + } + [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-1/src/osx/Preambles+Coder.h b/tikzit-1/src/osx/Preambles+Coder.h new file mode 100644 index 0000000..5a270c5 --- /dev/null +++ b/tikzit-1/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-1/src/osx/Preambles+Coder.m b/tikzit-1/src/osx/Preambles+Coder.m new file mode 100644 index 0000000..5e468b2 --- /dev/null +++ b/tikzit-1/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 { + if (!(self = [super init])) return nil; + 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-1/src/osx/PreferenceController.h b/tikzit-1/src/osx/PreferenceController.h new file mode 100644 index 0000000..b2b23f3 --- /dev/null +++ b/tikzit-1/src/osx/PreferenceController.h @@ -0,0 +1,49 @@ +// +// PreferenceController.h +// TikZiT +// +// Created by Karl Johan Paulsson on 26/02/2013. +// Copyright (c) 2013 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 "UpdatePreferenceController.h" +#import "PreambleController.h" +#import "CustomNodeController.h" + +@interface PreferenceController : NSWindowController{ + + IBOutlet NSView *engineView; + IBOutlet NSView *generalView; + IBOutlet NSView *updateView; + IBOutlet NSView *preambleView; + IBOutlet NSView *customNodeView; + + UpdatePreferenceController *updateController; + PreambleController *preambleController; + CustomNodeController *customNodeController; + + int currentViewTag; +} + +- (id)initWithWindowNibName:(NSString *)windowNibName preambleController:(PreambleController *)pc; + +- (IBAction)switchView:(id)sender; + +@end diff --git a/tikzit-1/src/osx/PreferenceController.m b/tikzit-1/src/osx/PreferenceController.m new file mode 100644 index 0000000..e785358 --- /dev/null +++ b/tikzit-1/src/osx/PreferenceController.m @@ -0,0 +1,133 @@ +// +// PreferenceController.m +// TikZiT +// +// Created by Karl Johan Paulsson on 26/02/2013. +// Copyright (c) 2013 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 "PreferenceController.h" + +@interface PreferenceController () + +@end + +@implementation PreferenceController + +- (id)initWithWindowNibName:(NSString *)windowNibName preambleController:(PreambleController *)pc{ + if (!(self = [super initWithWindowNibName:windowNibName])) return nil; + + preambleController = pc; + + return self; +} + +- (NSRect)newFrameForNewContentView:(NSView*)view { + NSWindow *window = [self window]; + NSRect newFrameRect = [window frameRectForContentRect:[view frame]]; + NSRect oldFrameRect = [window frame]; + NSSize newSize = newFrameRect.size; + NSSize oldSize = oldFrameRect.size; + + NSRect frame = [window frame]; + frame.size = newSize; + frame.origin.y -= (newSize.height - oldSize.height); + + return frame; +} + +- (NSView *)viewForTag:(int)tag { + + NSView *view = nil; + switch (tag) { + default: + case 0: + view = generalView; + break; + case 1: + view = engineView; + break; + case 2: + view = updateView; + break; + case 3: + view = preambleView; + break; + case 4: + view = customNodeView; + break; + } + + return view; +} + +- (BOOL)validateToolbarItem:(NSToolbarItem *)item { + + if ([item tag] == currentViewTag) return NO; + else return YES; + +} + +- (void)awakeFromNib { + + [[self window] setContentSize:[generalView frame].size]; + [[[self window] contentView] addSubview:generalView]; + [[[self window] contentView] setWantsLayer:YES]; + + updateController = [[UpdatePreferenceController alloc] initWithNibName:@"UpdatePreferencePanel" bundle:nil]; + [[updateController view] setFrame:[updateView frame]]; + [[[self window] contentView] replaceSubview:updateView with:[updateController view]]; + updateView = [updateController view]; + + [[preambleController view] setFrame:[preambleView frame]]; + [[[self window] contentView] replaceSubview:preambleView with:[preambleController view]]; + preambleView = [preambleController view]; + + customNodeController = [[CustomNodeController alloc] initWithNibName:@"CustomNodes" bundle:nil]; + [[customNodeController view] setFrame:[customNodeView frame]]; + [[[self window] contentView] replaceSubview:customNodeView with:[customNodeController view]]; + customNodeView = [customNodeController view]; + + [[self window] setContentSize:[engineView frame].size]; + [[[self window] contentView] addSubview:engineView]; + currentViewTag = 1; +} + +- (IBAction)switchView:(id)sender { + + int tag = [sender tag]; + NSView *view = [self viewForTag:tag]; + NSView *previousView = [self viewForTag:currentViewTag]; + currentViewTag = tag; + + NSRect newFrame = [self newFrameForNewContentView:view]; + + [NSAnimationContext beginGrouping]; + + if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) + [[NSAnimationContext currentContext] setDuration:1.0]; + + [[[[self window] contentView] animator] replaceSubview:previousView with:view]; + [[[self window] animator] setFrame:newFrame display:YES]; + + [NSAnimationContext endGrouping]; + +} + +@end diff --git a/tikzit-1/src/osx/Preferences.xib b/tikzit-1/src/osx/Preferences.xib new file mode 100644 index 0000000..1be3f9f --- /dev/null +++ b/tikzit-1/src/osx/Preferences.xib @@ -0,0 +1,1165 @@ +<?xml version="1.0" encoding="UTF-8"?> +<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00"> + <data> + <int key="IBDocument.SystemTarget">1070</int> + <string key="IBDocument.SystemVersion">13C64</string> + <string key="IBDocument.InterfaceBuilderVersion">5053</string> + <string key="IBDocument.AppKitVersion">1265.19</string> + <string key="IBDocument.HIToolboxVersion">697.40</string> + <object class="NSMutableDictionary" key="IBDocument.PluginVersions"> + <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="NS.object.0">5053</string> + </object> + <array key="IBDocument.IntegratedClassDependencies"> + <string>IBNSLayoutConstraint</string> + <string>NSButton</string> + <string>NSButtonCell</string> + <string>NSCustomObject</string> + <string>NSCustomView</string> + <string>NSTextField</string> + <string>NSTextFieldCell</string> + <string>NSToolbar</string> + <string>NSToolbarItem</string> + <string>NSUserDefaultsController</string> + <string>NSView</string> + <string>NSWindowTemplate</string> + </array> + <array key="IBDocument.PluginDependencies"> + <string>com.apple.InterfaceBuilder.CocoaPlugin</string> + </array> + <object class="NSMutableDictionary" key="IBDocument.Metadata"> + <string key="NS.key.0">PluginDependencyRecalculationVersion</string> + <integer value="1" key="NS.object.0"/> + </object> + <array class="NSMutableArray" key="IBDocument.RootObjects" id="1000"> + <object class="NSCustomObject" id="1001"> + <string key="NSClassName">PreferenceController</string> + </object> + <object class="NSCustomObject" id="1003"> + <string key="NSClassName">FirstResponder</string> + </object> + <object class="NSCustomObject" id="1004"> + <string key="NSClassName">NSApplication</string> + </object> + <object class="NSWindowTemplate" id="1005"> + <int key="NSWindowStyleMask">7</int> + <int key="NSWindowBacking">2</int> + <string key="NSWindowRect">{{196, 240}, {480, 270}}</string> + <int key="NSWTFlags">544736256</int> + <string key="NSWindowTitle">Preferences</string> + <string key="NSWindowClass">NSWindow</string> + <object class="NSToolbar" key="NSViewClass" id="338765480"> + <object class="NSMutableString" key="NSToolbarIdentifier"> + <characters key="NS.bytes">C1747407-DC9A-4297-9C1C-0A5010984E6C</characters> + </object> + <nil key="NSToolbarDelegate"/> + <bool key="NSToolbarPrefersToBeShown">YES</bool> + <bool key="NSToolbarShowsBaselineSeparator">YES</bool> + <bool key="NSToolbarAllowsUserCustomization">NO</bool> + <bool key="NSToolbarAutosavesConfiguration">NO</bool> + <int key="NSToolbarDisplayMode">1</int> + <int key="NSToolbarSizeMode">1</int> + <dictionary class="NSMutableDictionary" key="NSToolbarIBIdentifiedItems"> + <object class="NSToolbarItem" key="197F9408-AFB0-404B-B2B6-4DB1250B0A80" id="224936444"> + <object class="NSMutableString" key="NSToolbarItemIdentifier"> + <characters key="NS.bytes">197F9408-AFB0-404B-B2B6-4DB1250B0A80</characters> + </object> + <string key="NSToolbarItemLabel">Updates</string> + <string key="NSToolbarItemPaletteLabel">Updates</string> + <string key="NSToolbarItemToolTip"/> + <nil key="NSToolbarItemView"/> + <object class="NSCustomResource" key="NSToolbarItemImage"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">updates</string> + </object> + <nil key="NSToolbarItemTarget"/> + <nil key="NSToolbarItemAction"/> + <string key="NSToolbarItemMinSize">{0, 0}</string> + <string key="NSToolbarItemMaxSize">{0, 0}</string> + <bool key="NSToolbarItemEnabled">YES</bool> + <bool key="NSToolbarItemAutovalidates">YES</bool> + <int key="NSToolbarItemTag">2</int> + <bool key="NSToolbarIsUserRemovable">YES</bool> + <int key="NSToolbarItemVisibilityPriority">0</int> + </object> + <object class="NSToolbarItem" key="A3DDD070-5637-444B-92C6-905084CAC389" id="914743654"> + <object class="NSMutableString" key="NSToolbarItemIdentifier"> + <characters key="NS.bytes">A3DDD070-5637-444B-92C6-905084CAC389</characters> + </object> + <string key="NSToolbarItemLabel">General</string> + <string key="NSToolbarItemPaletteLabel">General</string> + <string key="NSToolbarItemToolTip"/> + <nil key="NSToolbarItemView"/> + <object class="NSCustomResource" key="NSToolbarItemImage"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">NSPreferencesGeneral</string> + </object> + <nil key="NSToolbarItemTarget"/> + <nil key="NSToolbarItemAction"/> + <string key="NSToolbarItemMinSize">{0, 0}</string> + <string key="NSToolbarItemMaxSize">{0, 0}</string> + <bool key="NSToolbarItemEnabled">YES</bool> + <bool key="NSToolbarItemAutovalidates">YES</bool> + <int key="NSToolbarItemTag">1</int> + <bool key="NSToolbarIsUserRemovable">YES</bool> + <int key="NSToolbarItemVisibilityPriority">0</int> + </object> + <object class="NSToolbarItem" key="A96DC4D4-2171-4D05-8C08-8D01B3829158" id="265637031"> + <object class="NSMutableString" key="NSToolbarItemIdentifier"> + <characters key="NS.bytes">A96DC4D4-2171-4D05-8C08-8D01B3829158</characters> + </object> + <string key="NSToolbarItemLabel">Preamble</string> + <string key="NSToolbarItemPaletteLabel">Preamble</string> + <string key="NSToolbarItemToolTip"/> + <nil key="NSToolbarItemView"/> + <object class="NSCustomResource" key="NSToolbarItemImage"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">preamble</string> + </object> + <nil key="NSToolbarItemTarget"/> + <nil key="NSToolbarItemAction"/> + <string key="NSToolbarItemMinSize">{0, 0}</string> + <string key="NSToolbarItemMaxSize">{0, 0}</string> + <bool key="NSToolbarItemEnabled">YES</bool> + <bool key="NSToolbarItemAutovalidates">YES</bool> + <int key="NSToolbarItemTag">3</int> + <bool key="NSToolbarIsUserRemovable">YES</bool> + <int key="NSToolbarItemVisibilityPriority">0</int> + </object> + <object class="NSToolbarItem" key="CBA2626C-DD4C-4ADD-BD5D-26D21216D9A8" id="845520355"> + <object class="NSMutableString" key="NSToolbarItemIdentifier"> + <characters key="NS.bytes">CBA2626C-DD4C-4ADD-BD5D-26D21216D9A8</characters> + </object> + <string key="NSToolbarItemLabel">Custom Nodes</string> + <string key="NSToolbarItemPaletteLabel">Custom Nodes</string> + <string key="NSToolbarItemToolTip"/> + <nil key="NSToolbarItemView"/> + <object class="NSCustomResource" key="NSToolbarItemImage"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">customshape</string> + </object> + <nil key="NSToolbarItemTarget"/> + <nil key="NSToolbarItemAction"/> + <string key="NSToolbarItemMinSize">{0, 0}</string> + <string key="NSToolbarItemMaxSize">{0, 0}</string> + <bool key="NSToolbarItemEnabled">YES</bool> + <bool key="NSToolbarItemAutovalidates">YES</bool> + <int key="NSToolbarItemTag">4</int> + <bool key="NSToolbarIsUserRemovable">YES</bool> + <int key="NSToolbarItemVisibilityPriority">0</int> + </object> + <object class="NSToolbarItem" key="F85FE7C2-9847-4E58-8BF6-BE334E918CA7" id="641338426"> + <object class="NSMutableString" key="NSToolbarItemIdentifier"> + <characters key="NS.bytes">F85FE7C2-9847-4E58-8BF6-BE334E918CA7</characters> + </object> + <string key="NSToolbarItemLabel">Engine</string> + <string key="NSToolbarItemPaletteLabel">Engine</string> + <string key="NSToolbarItemToolTip"/> + <nil key="NSToolbarItemView"/> + <object class="NSCustomResource" key="NSToolbarItemImage"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">engine</string> + </object> + <nil key="NSToolbarItemTarget"/> + <nil key="NSToolbarItemAction"/> + <string key="NSToolbarItemMinSize">{0, 0}</string> + <string key="NSToolbarItemMaxSize">{0, 0}</string> + <bool key="NSToolbarItemEnabled">YES</bool> + <bool key="NSToolbarItemAutovalidates">YES</bool> + <int key="NSToolbarItemTag">1</int> + <bool key="NSToolbarIsUserRemovable">YES</bool> + <int key="NSToolbarItemVisibilityPriority">0</int> + </object> + </dictionary> + <array class="NSMutableArray" key="NSToolbarIBAllowedItems"> + <reference ref="914743654"/> + <reference ref="224936444"/> + <reference ref="641338426"/> + <reference ref="265637031"/> + <reference ref="845520355"/> + </array> + <array key="NSToolbarIBDefaultItems"> + <reference ref="914743654"/> + <reference ref="265637031"/> + <reference ref="845520355"/> + <reference ref="224936444"/> + </array> + <array key="NSToolbarIBSelectableItems" id="0"/> + </object> + <nil key="NSUserInterfaceItemIdentifier"/> + <object class="NSView" key="NSWindowView" id="1006"> + <reference key="NSNextResponder"/> + <string key="NSFrameSize">{480, 270}</string> + <reference key="NSSuperview"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView"/> + </object> + <string key="NSScreenRect">{{0, 0}, {1680, 1028}}</string> + <string key="NSMaxSize">{10000000000000, 10000000000000}</string> + <bool key="NSWindowIsRestorable">YES</bool> + </object> + <object class="NSCustomView" id="103531975"> + <reference key="NSNextResponder"/> + <int key="NSvFlags">268</int> + <array class="NSMutableArray" key="NSSubviews"> + <object class="NSButton" id="521024449"> + <reference key="NSNextResponder" ref="103531975"/> + <int key="NSvFlags">268</int> + <string key="NSFrame">{{18, 106}, {219, 18}}</string> + <reference key="NSSuperview" ref="103531975"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView"/> + <string key="NSReuseIdentifierKey">_NS:9</string> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="550678479"> + <int key="NSCellFlags">-2080374784</int> + <int key="NSCellFlags2">268435456</int> + <string key="NSContents">Keep inspector windows on top</string> + <object class="NSFont" key="NSSupport" id="924461577"> + <string key="NSName">.LucidaGrandeUI</string> + <double key="NSSize">13</double> + <int key="NSfFlags">1044</int> + </object> + <string key="NSCellIdentifier">_NS:9</string> + <reference key="NSControlView" ref="521024449"/> + <int key="NSButtonFlags">1211912448</int> + <int key="NSButtonFlags2">2</int> + <object class="NSCustomResource" key="NSNormalImage" id="959728078"> + <string key="NSClassName">NSImage</string> + <string key="NSResourceName">NSSwitch</string> + </object> + <object class="NSButtonImageSource" key="NSAlternateImage" id="808033943"> + <string key="NSImageName">NSSwitch</string> + </object> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + <bool key="NSAllowsLogicalLayoutDirection">NO</bool> + </object> + <object class="NSButton" id="118735803"> + <reference key="NSNextResponder" ref="103531975"/> + <int key="NSvFlags">268</int> + <string key="NSFrame">{{18, 126}, {168, 18}}</string> + <reference key="NSSuperview" ref="103531975"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="521024449"/> + <string key="NSReuseIdentifierKey">_NS:9</string> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="63691474"> + <int key="NSCellFlags">-2080374784</int> + <int key="NSCellFlags2">268435456</int> + <string key="NSContents">Autocomplete brackets</string> + <reference key="NSSupport" ref="924461577"/> + <string key="NSCellIdentifier">_NS:9</string> + <reference key="NSControlView" ref="118735803"/> + <int key="NSButtonFlags">1211912448</int> + <int key="NSButtonFlags2">2</int> + <reference key="NSNormalImage" ref="959728078"/> + <reference key="NSAlternateImage" ref="808033943"/> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + <bool key="NSAllowsLogicalLayoutDirection">NO</bool> + </object> + <object class="NSButton" id="764495169"> + <reference key="NSNextResponder" ref="103531975"/> + <int key="NSvFlags">268</int> + <string key="NSFrame">{{18, 18}, {214, 18}}</string> + <reference key="NSSuperview" ref="103531975"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView"/> + <string key="NSReuseIdentifierKey">_NS:9</string> + <bool key="NSEnabled">YES</bool> + <object class="NSButtonCell" key="NSCell" id="292348615"> + <int key="NSCellFlags">-2080374784</int> + <int key="NSCellFlags2">268435456</int> + <string key="NSContents">Bring preview window to focus</string> + <reference key="NSSupport" ref="924461577"/> + <string key="NSCellIdentifier">_NS:9</string> + <reference key="NSControlView" ref="764495169"/> + <int key="NSButtonFlags">1211912448</int> + <int key="NSButtonFlags2">2</int> + <reference key="NSNormalImage" ref="959728078"/> + <reference key="NSAlternateImage" ref="808033943"/> + <string key="NSAlternateContents"/> + <string key="NSKeyEquivalent"/> + <int key="NSPeriodicDelay">200</int> + <int key="NSPeriodicInterval">25</int> + </object> + <bool key="NSAllowsLogicalLayoutDirection">NO</bool> + </object> + <object class="NSTextField" id="762565485"> + <reference key="NSNextResponder" ref="103531975"/> + <int key="NSvFlags">268</int> + <string key="NSFrame">{{20, 42}, {440, 22}}</string> + <reference key="NSSuperview" ref="103531975"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="764495169"/> + <string key="NSReuseIdentifierKey">_NS:9</string> + <bool key="NSEnabled">YES</bool> + <object class="NSTextFieldCell" key="NSCell" id="186397132"> + <int key="NSCellFlags">-1804599231</int> + <int key="NSCellFlags2">272630784</int> + <string key="NSContents"/> + <reference key="NSSupport" ref="924461577"/> + <string key="NSCellIdentifier">_NS:9</string> + <reference key="NSControlView" ref="762565485"/> + <bool key="NSDrawsBackground">YES</bool> + <object class="NSColor" key="NSBackgroundColor"> + <int key="NSColorSpace">6</int> + <string key="NSCatalogName">System</string> + <string key="NSColorName">textBackgroundColor</string> + <object class="NSColor" key="NSColor"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MQA</bytes> + </object> + </object> + <object class="NSColor" key="NSTextColor"> + <int key="NSColorSpace">6</int> + <string key="NSCatalogName">System</string> + <string key="NSColorName">textColor</string> + <object class="NSColor" key="NSColor" id="433834218"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MAA</bytes> + </object> + </object> + </object> + <bool key="NSAllowsLogicalLayoutDirection">NO</bool> + <int key="NSTextFieldAlignmentRectInsetsVersion">1</int> + </object> + <object class="NSTextField" id="539123669"> + <reference key="NSNextResponder" ref="103531975"/> + <int key="NSvFlags">268</int> + <string key="NSFrame">{{18, 72}, {140, 17}}</string> + <reference key="NSSuperview" ref="103531975"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="762565485"/> + <string key="NSReuseIdentifierKey">_NS:1535</string> + <bool key="NSEnabled">YES</bool> + <object class="NSTextFieldCell" key="NSCell" id="459248896"> + <int key="NSCellFlags">68157504</int> + <int key="NSCellFlags2">272630784</int> + <string key="NSContents">pdfLaTeX source path</string> + <reference key="NSSupport" ref="924461577"/> + <string key="NSCellIdentifier">_NS:1535</string> + <reference key="NSControlView" ref="539123669"/> + <object class="NSColor" key="NSBackgroundColor"> + <int key="NSColorSpace">6</int> + <string key="NSCatalogName">System</string> + <string key="NSColorName">controlColor</string> + <object class="NSColor" key="NSColor"> + <int key="NSColorSpace">3</int> + <bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes> + </object> + </object> + <object class="NSColor" key="NSTextColor"> + <int key="NSColorSpace">6</int> + <string key="NSCatalogName">System</string> + <string key="NSColorName">controlTextColor</string> + <reference key="NSColor" ref="433834218"/> + </object> + </object> + <bool key="NSAllowsLogicalLayoutDirection">NO</bool> + <int key="NSTextFieldAlignmentRectInsetsVersion">1</int> + </object> + </array> + <string key="NSFrameSize">{480, 162}</string> + <reference key="NSSuperview"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView" ref="118735803"/> + <string key="NSReuseIdentifierKey">_NS:9</string> + <string key="NSClassName">NSView</string> + </object> + <object class="NSCustomView" id="596032684"> + <reference key="NSNextResponder"/> + <int key="NSvFlags">268</int> + <array class="NSMutableArray" key="NSSubviews"/> + <string key="NSFrameSize">{480, 96}</string> + <reference key="NSSuperview"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView"/> + <string key="NSReuseIdentifierKey">_NS:9</string> + <string key="NSClassName">NSView</string> + </object> + <object class="NSCustomView" id="169128791"> + <reference key="NSNextResponder"/> + <int key="NSvFlags">12</int> + <array class="NSMutableArray" key="NSSubviews"/> + <string key="NSFrameSize">{480, 115}</string> + <reference key="NSSuperview"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView"/> + <string key="NSReuseIdentifierKey">_NS:9</string> + <string key="NSClassName">NSView</string> + </object> + <object class="NSCustomView" id="812696929"> + <reference key="NSNextResponder"/> + <int key="NSvFlags">12</int> + <array class="NSMutableArray" key="NSSubviews"/> + <string key="NSFrameSize">{557, 354}</string> + <reference key="NSSuperview"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView"/> + <string key="NSReuseIdentifierKey">_NS:9</string> + <string key="NSClassName">NSView</string> + </object> + <object class="NSCustomView" id="443314002"> + <reference key="NSNextResponder"/> + <int key="NSvFlags">12</int> + <array class="NSMutableArray" key="NSSubviews"/> + <string key="NSFrameSize">{557, 354}</string> + <reference key="NSSuperview"/> + <reference key="NSWindow"/> + <reference key="NSNextKeyView"/> + <string key="NSReuseIdentifierKey">_NS:9</string> + <string key="NSClassName">NSView</string> + </object> + <object class="NSUserDefaultsController" id="706090457"> + <bool key="NSSharedInstance">YES</bool> + </object> + </array> + <object class="IBObjectContainer" key="IBDocument.Objects"> + <array class="NSMutableArray" key="connectionRecords"> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">window</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="1005"/> + </object> + <int key="connectionID">3</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">engineView</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="103531975"/> + </object> + <int key="connectionID">23</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">generalView</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="596032684"/> + </object> + <int key="connectionID">25</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">switchView:</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="914743654"/> + </object> + <int key="connectionID">26</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">switchView:</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="641338426"/> + </object> + <int key="connectionID">27</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">switchView:</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="224936444"/> + </object> + <int key="connectionID">116</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">updateView</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="169128791"/> + </object> + <int key="connectionID">117</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">switchView:</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="265637031"/> + </object> + <int key="connectionID">120</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">preambleView</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="812696929"/> + </object> + <int key="connectionID">121</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">customNodeView</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="443314002"/> + </object> + <int key="connectionID">123</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBActionConnection" key="connection"> + <string key="label">switchView:</string> + <reference key="source" ref="1001"/> + <reference key="destination" ref="845520355"/> + </object> + <int key="connectionID">125</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBOutletConnection" key="connection"> + <string key="label">delegate</string> + <reference key="source" ref="1005"/> + <reference key="destination" ref="1001"/> + </object> + <int key="connectionID">4</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBBindingConnection" key="connection"> + <string key="label">value: values.net.sourceforge.tikzit.pdflatexpath</string> + <reference key="source" ref="186397132"/> + <reference key="destination" ref="706090457"/> + <object class="NSNibBindingConnector" key="connector"> + <reference key="NSSource" ref="186397132"/> + <reference key="NSDestination" ref="706090457"/> + <string key="NSLabel">value: values.net.sourceforge.tikzit.pdflatexpath</string> + <string key="NSBinding">value</string> + <string key="NSKeyPath">values.net.sourceforge.tikzit.pdflatexpath</string> + <int key="NSNibBindingConnectorVersion">2</int> + </object> + </object> + <int key="connectionID">54</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBBindingConnection" key="connection"> + <string key="label">value: values.net.sourceforge.tikzit.previewfocus</string> + <reference key="source" ref="292348615"/> + <reference key="destination" ref="706090457"/> + <object class="NSNibBindingConnector" key="connector"> + <reference key="NSSource" ref="292348615"/> + <reference key="NSDestination" ref="706090457"/> + <string key="NSLabel">value: values.net.sourceforge.tikzit.previewfocus</string> + <string key="NSBinding">value</string> + <string key="NSKeyPath">values.net.sourceforge.tikzit.previewfocus</string> + <int key="NSNibBindingConnectorVersion">2</int> + </object> + </object> + <int key="connectionID">62</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBBindingConnection" key="connection"> + <string key="label">value: values.net.sourceforge.tikzit.autocomplete</string> + <reference key="source" ref="118735803"/> + <reference key="destination" ref="706090457"/> + <object class="NSNibBindingConnector" key="connector"> + <reference key="NSSource" ref="118735803"/> + <reference key="NSDestination" ref="706090457"/> + <string key="NSLabel">value: values.net.sourceforge.tikzit.autocomplete</string> + <string key="NSBinding">value</string> + <string key="NSKeyPath">values.net.sourceforge.tikzit.autocomplete</string> + <int key="NSNibBindingConnectorVersion">2</int> + </object> + </object> + <int key="connectionID">149</int> + </object> + <object class="IBConnectionRecord"> + <object class="IBBindingConnection" key="connection"> + <string key="label">value: values.net.sourceforge.tikzit.inspectorsontop</string> + <reference key="source" ref="521024449"/> + <reference key="destination" ref="706090457"/> + <object class="NSNibBindingConnector" key="connector"> + <reference key="NSSource" ref="521024449"/> + <reference key="NSDestination" ref="706090457"/> + <string key="NSLabel">value: values.net.sourceforge.tikzit.inspectorsontop</string> + <string key="NSBinding">value</string> + <string key="NSKeyPath">values.net.sourceforge.tikzit.inspectorsontop</string> + <int key="NSNibBindingConnectorVersion">2</int> + </object> + </object> + <int key="connectionID">150</int> + </object> + </array> + <object class="IBMutableOrderedSet" key="objectRecords"> + <array key="orderedObjects"> + <object class="IBObjectRecord"> + <int key="objectID">0</int> + <reference key="object" ref="0"/> + <reference key="children" ref="1000"/> + <nil key="parent"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-2</int> + <reference key="object" ref="1001"/> + <reference key="parent" ref="0"/> + <string key="objectName">File's Owner</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-1</int> + <reference key="object" ref="1003"/> + <reference key="parent" ref="0"/> + <string key="objectName">First Responder</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">-3</int> + <reference key="object" ref="1004"/> + <reference key="parent" ref="0"/> + <string key="objectName">Application</string> + </object> + <object class="IBObjectRecord"> + <int key="objectID">1</int> + <reference key="object" ref="1005"/> + <array class="NSMutableArray" key="children"> + <reference ref="1006"/> + <reference ref="338765480"/> + </array> + <reference key="parent" ref="0"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">2</int> + <reference key="object" ref="1006"/> + <reference key="parent" ref="1005"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">5</int> + <reference key="object" ref="338765480"/> + <array class="NSMutableArray" key="children"> + <reference ref="641338426"/> + <reference ref="914743654"/> + <reference ref="224936444"/> + <reference ref="265637031"/> + <reference ref="845520355"/> + </array> + <reference key="parent" ref="1005"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">11</int> + <reference key="object" ref="641338426"/> + <reference key="parent" ref="338765480"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">12</int> + <reference key="object" ref="914743654"/> + <reference key="parent" ref="338765480"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">13</int> + <reference key="object" ref="103531975"/> + <array class="NSMutableArray" key="children"> + <object class="IBNSLayoutConstraint" id="727314944"> + <reference key="firstItem" ref="103531975"/> + <int key="firstAttribute">4</int> + <int key="relation">0</int> + <reference key="secondItem" ref="764495169"/> + <int key="secondAttribute">4</int> + <float key="multiplier">1</float> + <string key="multiplierString">1</string> + <object class="IBNSLayoutSymbolicConstant" key="constant"> + <double key="value">20</double> + </object> + <float key="priority">1000</float> + <reference key="containingView" ref="103531975"/> + <int key="scoringType">8</int> + <float key="scoringTypeFloat">23</float> + <int key="contentType">3</int> + <bool key="placeholder">NO</bool> + </object> + <object class="IBNSLayoutConstraint" id="728277728"> + <reference key="firstItem" ref="764495169"/> + <int key="firstAttribute">3</int> + <int key="relation">0</int> + <reference key="secondItem" ref="762565485"/> + <int key="secondAttribute">4</int> + <float key="multiplier">1</float> + <string key="multiplierString">1</string> + <object class="IBNSLayoutSymbolicConstant" key="constant"> + <double key="value">8</double> + </object> + <float key="priority">1000</float> + <reference key="containingView" ref="103531975"/> + <int key="scoringType">6</int> + <float key="scoringTypeFloat">24</float> + <int key="contentType">3</int> + <bool key="placeholder">NO</bool> + </object> + <object class="IBNSLayoutConstraint" id="116293249"> + <reference key="firstItem" ref="764495169"/> + <int key="firstAttribute">5</int> + <int key="relation">0</int> + <reference key="secondItem" ref="103531975"/> + <int key="secondAttribute">5</int> + <float key="multiplier">1</float> + <string key="multiplierString">1</string> + <object class="IBNSLayoutSymbolicConstant" key="constant"> + <double key="value">20</double> + </object> + <float key="priority">1000</float> + <reference key="containingView" ref="103531975"/> + <int key="scoringType">0</int> + <float key="scoringTypeFloat">29</float> + <int key="contentType">3</int> + <bool key="placeholder">NO</bool> + </object> + <object class="IBNSLayoutConstraint" id="206376115"> + <reference key="firstItem" ref="103531975"/> + <int key="firstAttribute">6</int> + <int key="relation">0</int> + <reference key="secondItem" ref="762565485"/> + <int key="secondAttribute">6</int> + <float key="multiplier">1</float> + <string key="multiplierString">1</string> + <object class="IBNSLayoutSymbolicConstant" key="constant"> + <double key="value">20</double> + </object> + <float key="priority">1000</float> + <reference key="containingView" ref="103531975"/> + <int key="scoringType">0</int> + <float key="scoringTypeFloat">29</float> + <int key="contentType">3</int> + <bool key="placeholder">NO</bool> + </object> + <object class="IBNSLayoutConstraint" id="921593038"> + <reference key="firstItem" ref="762565485"/> + <int key="firstAttribute">3</int> + <int key="relation">0</int> + <reference key="secondItem" ref="539123669"/> + <int key="secondAttribute">4</int> + <float key="multiplier">1</float> + <string key="multiplierString">1</string> + <object class="IBNSLayoutSymbolicConstant" key="constant"> + <double key="value">8</double> + </object> + <float key="priority">1000</float> + <reference key="containingView" ref="103531975"/> + <int key="scoringType">6</int> + <float key="scoringTypeFloat">24</float> + <int key="contentType">3</int> + <bool key="placeholder">NO</bool> + </object> + <object class="IBNSLayoutConstraint" id="783720041"> + <reference key="firstItem" ref="762565485"/> + <int key="firstAttribute">5</int> + <int key="relation">0</int> + <reference key="secondItem" ref="103531975"/> + <int key="secondAttribute">5</int> + <float key="multiplier">1</float> + <string key="multiplierString">1</string> + <object class="IBNSLayoutSymbolicConstant" key="constant"> + <double key="value">20</double> + </object> + <float key="priority">1000</float> + <reference key="containingView" ref="103531975"/> + <int key="scoringType">0</int> + <float key="scoringTypeFloat">29</float> + <int key="contentType">3</int> + <bool key="placeholder">NO</bool> + </object> + <object class="IBNSLayoutConstraint" id="559306534"> + <reference key="firstItem" ref="539123669"/> + <int key="firstAttribute">5</int> + <int key="relation">0</int> + <reference key="secondItem" ref="103531975"/> + <int key="secondAttribute">5</int> + <float key="multiplier">1</float> + <string key="multiplierString">1</string> + <object class="IBNSLayoutSymbolicConstant" key="constant"> + <double key="value">20</double> + </object> + <float key="priority">1000</float> + <reference key="containingView" ref="103531975"/> + <int key="scoringType">0</int> + <float key="scoringTypeFloat">29</float> + <int key="contentType">3</int> + <bool key="placeholder">NO</bool> + </object> + <object class="IBNSLayoutConstraint" id="852545348"> + <reference key="firstItem" ref="521024449"/> + <int key="firstAttribute">5</int> + <int key="relation">0</int> + <reference key="secondItem" ref="539123669"/> + <int key="secondAttribute">5</int> + <float key="multiplier">1</float> + <string key="multiplierString">1</string> + <object class="IBLayoutConstant" key="constant"> + <double key="value">0.0</double> + </object> + <float key="priority">1000</float> + <reference key="containingView" ref="103531975"/> + <int key="scoringType">6</int> + <float key="scoringTypeFloat">24</float> + <int key="contentType">2</int> + <bool key="placeholder">NO</bool> + </object> + <object class="IBNSLayoutConstraint" id="750010708"> + <reference key="firstItem" ref="521024449"/> + <int key="firstAttribute">3</int> + <int key="relation">0</int> + <reference key="secondItem" ref="118735803"/> + <int key="secondAttribute">4</int> + <float key="multiplier">1</float> + <string key="multiplierString">1</string> + <object class="IBNSLayoutSymbolicConstant" key="constant"> + <double key="value">6</double> + </object> + <float key="priority">1000</float> + <reference key="containingView" ref="103531975"/> + <int key="scoringType">6</int> + <float key="scoringTypeFloat">24</float> + <int key="contentType">3</int> + <bool key="placeholder">NO</bool> + </object> + <object class="IBNSLayoutConstraint" id="295298081"> + <reference key="firstItem" ref="521024449"/> + <int key="firstAttribute">5</int> + <int key="relation">0</int> + <reference key="secondItem" ref="118735803"/> + <int key="secondAttribute">5</int> + <float key="multiplier">1</float> + <string key="multiplierString">1</string> + <object class="IBLayoutConstant" key="constant"> + <double key="value">0.0</double> + </object> + <float key="priority">1000</float> + <reference key="containingView" ref="103531975"/> + <int key="scoringType">6</int> + <float key="scoringTypeFloat">24</float> + <int key="contentType">2</int> + <bool key="placeholder">NO</bool> + </object> + <object class="IBNSLayoutConstraint" id="956205451"> + <reference key="firstItem" ref="118735803"/> + <int key="firstAttribute">3</int> + <int key="relation">0</int> + <reference key="secondItem" ref="103531975"/> + <int key="secondAttribute">3</int> + <float key="multiplier">1</float> + <string key="multiplierString">1</string> + <object class="IBNSLayoutSymbolicConstant" key="constant"> + <double key="value">20</double> + </object> + <float key="priority">1000</float> + <reference key="containingView" ref="103531975"/> + <int key="scoringType">8</int> + <float key="scoringTypeFloat">23</float> + <int key="contentType">3</int> + <bool key="placeholder">NO</bool> + </object> + <reference ref="539123669"/> + <reference ref="762565485"/> + <reference ref="764495169"/> + <reference ref="118735803"/> + <reference ref="521024449"/> + </array> + <reference key="parent" ref="0"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">14</int> + <reference key="object" ref="596032684"/> + <array class="NSMutableArray" key="children"/> + <reference key="parent" ref="0"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">15</int> + <reference key="object" ref="539123669"/> + <array class="NSMutableArray" key="children"> + <reference ref="459248896"/> + </array> + <reference key="parent" ref="103531975"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">16</int> + <reference key="object" ref="459248896"/> + <reference key="parent" ref="539123669"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">30</int> + <reference key="object" ref="559306534"/> + <reference key="parent" ref="103531975"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">32</int> + <reference key="object" ref="762565485"/> + <array class="NSMutableArray" key="children"> + <reference ref="186397132"/> + </array> + <reference key="parent" ref="103531975"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">33</int> + <reference key="object" ref="186397132"/> + <reference key="parent" ref="762565485"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">36</int> + <reference key="object" ref="783720041"/> + <reference key="parent" ref="103531975"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">39</int> + <reference key="object" ref="706090457"/> + <reference key="parent" ref="0"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">55</int> + <reference key="object" ref="921593038"/> + <reference key="parent" ref="103531975"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">56</int> + <reference key="object" ref="206376115"/> + <reference key="parent" ref="103531975"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">57</int> + <reference key="object" ref="764495169"/> + <array class="NSMutableArray" key="children"> + <reference ref="292348615"/> + </array> + <reference key="parent" ref="103531975"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">58</int> + <reference key="object" ref="292348615"/> + <reference key="parent" ref="764495169"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">59</int> + <reference key="object" ref="116293249"/> + <reference key="parent" ref="103531975"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">60</int> + <reference key="object" ref="728277728"/> + <reference key="parent" ref="103531975"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">64</int> + <reference key="object" ref="169128791"/> + <array class="NSMutableArray" key="children"/> + <reference key="parent" ref="0"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">115</int> + <reference key="object" ref="224936444"/> + <reference key="parent" ref="338765480"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">118</int> + <reference key="object" ref="812696929"/> + <reference key="parent" ref="0"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">119</int> + <reference key="object" ref="265637031"/> + <reference key="parent" ref="338765480"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">122</int> + <reference key="object" ref="443314002"/> + <reference key="parent" ref="0"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">124</int> + <reference key="object" ref="845520355"/> + <reference key="parent" ref="338765480"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">126</int> + <reference key="object" ref="727314944"/> + <reference key="parent" ref="103531975"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">127</int> + <reference key="object" ref="118735803"/> + <array class="NSMutableArray" key="children"> + <reference ref="63691474"/> + </array> + <reference key="parent" ref="103531975"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">128</int> + <reference key="object" ref="63691474"/> + <reference key="parent" ref="118735803"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">130</int> + <reference key="object" ref="956205451"/> + <reference key="parent" ref="103531975"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">131</int> + <reference key="object" ref="521024449"/> + <array class="NSMutableArray" key="children"> + <reference ref="550678479"/> + </array> + <reference key="parent" ref="103531975"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">132</int> + <reference key="object" ref="550678479"/> + <reference key="parent" ref="521024449"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">134</int> + <reference key="object" ref="295298081"/> + <reference key="parent" ref="103531975"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">135</int> + <reference key="object" ref="750010708"/> + <reference key="parent" ref="103531975"/> + </object> + <object class="IBObjectRecord"> + <int key="objectID">153</int> + <reference key="object" ref="852545348"/> + <reference key="parent" ref="103531975"/> + </object> + </array> + </object> + <dictionary class="NSMutableDictionary" key="flattenedProperties"> + <string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="1.IBWindowTemplateEditedContentRect">{{357, 418}, {480, 270}}</string> + <boolean value="NO" key="1.NSWindowTemplate.visibleAtLaunch"/> + <string key="11.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="115.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="118.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="119.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="12.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="122.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="124.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="126.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="NO" key="127.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/> + <string key="127.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="128.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <array key="13.IBNSViewMetadataConstraints"> + <reference ref="956205451"/> + <reference ref="295298081"/> + <reference ref="750010708"/> + <reference ref="852545348"/> + <reference ref="559306534"/> + <reference ref="783720041"/> + <reference ref="921593038"/> + <reference ref="206376115"/> + <reference ref="116293249"/> + <reference ref="728277728"/> + <reference ref="727314944"/> + </array> + <string key="13.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="130.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="NO" key="131.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/> + <string key="131.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="132.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="134.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="135.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="14.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="NO" key="15.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/> + <string key="15.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="153.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="16.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="30.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="NO" key="32.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/> + <string key="32.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="33.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="36.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="39.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="5.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="55.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="56.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <boolean value="NO" key="57.IBNSViewMetadataTranslatesAutoresizingMaskIntoConstraints"/> + <string key="57.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="58.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="59.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="60.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + <string key="64.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> + </dictionary> + <dictionary class="NSMutableDictionary" key="unlocalizedProperties"/> + <nil key="activeLocalization"/> + <dictionary class="NSMutableDictionary" key="localizations"/> + <nil key="sourceID"/> + <int key="maxID">153</int> + </object> + <object class="IBClassDescriber" key="IBDocument.Classes"> + <array class="NSMutableArray" key="referencedPartialClassDescriptions"> + <object class="IBPartialClassDescription"> + <string key="className">PreferenceController</string> + <string key="superclassName">NSWindowController</string> + <object class="NSMutableDictionary" key="actions"> + <string key="NS.key.0">switchView:</string> + <string key="NS.object.0">id</string> + </object> + <object class="NSMutableDictionary" key="actionInfosByName"> + <string key="NS.key.0">switchView:</string> + <object class="IBActionInfo" key="NS.object.0"> + <string key="name">switchView:</string> + <string key="candidateClassName">id</string> + </object> + </object> + <dictionary class="NSMutableDictionary" key="outlets"> + <string key="customNodeView">NSView</string> + <string key="engineView">NSView</string> + <string key="generalView">NSView</string> + <string key="preambleView">NSView</string> + <string key="updateView">NSView</string> + </dictionary> + <dictionary class="NSMutableDictionary" key="toOneOutletInfosByName"> + <object class="IBToOneOutletInfo" key="customNodeView"> + <string key="name">customNodeView</string> + <string key="candidateClassName">NSView</string> + </object> + <object class="IBToOneOutletInfo" key="engineView"> + <string key="name">engineView</string> + <string key="candidateClassName">NSView</string> + </object> + <object class="IBToOneOutletInfo" key="generalView"> + <string key="name">generalView</string> + <string key="candidateClassName">NSView</string> + </object> + <object class="IBToOneOutletInfo" key="preambleView"> + <string key="name">preambleView</string> + <string key="candidateClassName">NSView</string> + </object> + <object class="IBToOneOutletInfo" key="updateView"> + <string key="name">updateView</string> + <string key="candidateClassName">NSView</string> + </object> + </dictionary> + <object class="IBClassDescriptionSource" key="sourceIdentifier"> + <string key="majorKey">IBProjectSource</string> + <string key="minorKey">./Classes/PreferenceController.h</string> + </object> + </object> + </array> + </object> + <int key="IBDocument.localizationMode">0</int> + <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string> + <bool key="IBDocument.previouslyAttemptedUpgradeToXcode5">YES</bool> + <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults"> + <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string> + <real value="1070" key="NS.object.0"/> + </object> + <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies"> + <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3</string> + <integer value="4600" key="NS.object.0"/> + </object> + <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool> + <int key="IBDocument.defaultPropertyAccessControl">3</int> + <dictionary class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes"> + <string key="NSPreferencesGeneral">{32, 32}</string> + <string key="NSSwitch">{15, 15}</string> + <string key="customshape">{32, 32}</string> + <string key="engine">{32, 32}</string> + <string key="preamble">{32, 32}</string> + <string key="updates">{32, 32}</string> + </dictionary> + <bool key="IBDocument.UseAutolayout">YES</bool> + </data> +</archive> diff --git a/tikzit-1/src/osx/PreviewController.h b/tikzit-1/src/osx/PreviewController.h new file mode 100644 index 0000000..6c51a23 --- /dev/null +++ b/tikzit-1/src/osx/PreviewController.h @@ -0,0 +1,52 @@ +// +// 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> +#import "DraggablePDFView.h" + +@class PDFView; +@class PreambleController; + +@interface PreviewController : NSWindowController { + IBOutlet DraggablePDFView *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-1/src/osx/PreviewController.m b/tikzit-1/src/osx/PreviewController.m new file mode 100644 index 0000000..cf069b1 --- /dev/null +++ b/tikzit-1/src/osx/PreviewController.m @@ -0,0 +1,147 @@ +// +// 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 { + if (!(self = [super initWithWindowNibName:nib])) return nil; + 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]; + + if([[NSUserDefaults standardUserDefaults] boolForKey:@"net.sourceforge.tikzit.previewfocus"]){ + [[preview window] makeKeyAndOrderFront:self]; + } + + int fnum = typesetCount++; + + NSString *tex = [preambleController buildDocumentForTikz:tikz]; + + 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]; + + NSString *pdflatexPath = [[NSUserDefaults standardUserDefaults] stringForKey:@"net.sourceforge.tikzit.pdflatexpath"]; + + // 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" + @"%@ -interaction=nonstopmode -output-format=pdf -halt-on-error '%@'\n", pdflatexPath, texFile]; + + NSLog(@"Telling bash: %@", latexCmd); + + 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]; + + + NSData *data = [latexOut readDataToEndOfFile]; + NSString *str = [[NSString alloc] initWithData:data + encoding:NSUTF8StringEncoding]; + + [latexTask waitUntilExit]; + if ([latexTask terminationStatus] != 0) { + if ([latexTask terminationStatus] == 127) { + [errorTextView setHidden:YES]; + [errorText setString:@"\nCouldn't find pdflatex, change settings and try again."]; + [errorTextView setHidden:NO]; + }else{ + [errorTextView setHidden:YES]; + [errorText setString:[@"\nAN ERROR HAS OCCURRED, PDFLATEX SAID:\n\n" stringByAppendingString:str]]; + [errorTextView setHidden:NO]; + } + } else { + [errorText setString:@""]; + [errorTextView setHidden:YES]; + + PDFDocument *doc = [[PDFDocument alloc] initWithURL:[[NSURL alloc] initFileURLWithPath:pdfFile]]; + + // 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-1/src/osx/PropertyInspectorController.h b/tikzit-1/src/osx/PropertyInspectorController.h new file mode 100644 index 0000000..663ee4a --- /dev/null +++ b/tikzit-1/src/osx/PropertyInspectorController.h @@ -0,0 +1,83 @@ +// +// 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 NSComboBox *sourceAnchorComboBox; + IBOutlet NSComboBox *targetAnchorComboBox; + IBOutlet NSTextField *edgeNodeLabelField; + IBOutlet NSButton *edgeNodeCheckbox; + IBOutlet NSArrayController *nodeDataArrayController; + IBOutlet NSArrayController *graphDataArrayController; + IBOutlet NSArrayController *edgeDataArrayController; + IBOutlet NSArrayController *edgeNodeDataArrayController; + + NSMutableArray *sourceAnchorNames; + IBOutlet NSArrayController *sourceAnchorNamesArrayController; + + NSMutableArray *targetAnchorNames; + IBOutlet NSArrayController *targetAnchorNamesArrayController; + + 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 (strong) NSMutableArray *selectedNodes; +@property (strong) NSMutableArray *selectedEdges; +@property (strong) NSMutableArray *sourceAnchorNames; +@property (strong) NSMutableArray *targetAnchorNames; +@property (strong) 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-1/src/osx/PropertyInspectorController.m b/tikzit-1/src/osx/PropertyInspectorController.m new file mode 100644 index 0000000..039a30f --- /dev/null +++ b/tikzit-1/src/osx/PropertyInspectorController.m @@ -0,0 +1,280 @@ +// +// 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; +@synthesize sourceAnchorNames, targetAnchorNames; + +- (id)initWithWindowNibName:(NSString *)windowNibName { + if (!(self = [super initWithWindowNibName:windowNibName])) return nil; + + 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 setSourceAnchorNames: [[NSMutableArray alloc] initWithArray: [@"north south west east" componentsSeparatedByString:@" "]]]; + + [self setTargetAnchorNames: [[NSMutableArray alloc] initWithArray:[@"north south west east" componentsSeparatedByString:@" "]]]; + + + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"net.sourceforge.tikzit.inspectorsontop"] == YES) { + [[self window] setLevel:NSFloatingWindowLevel]; + } else { + [[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-1/src/osx/SelectBoxLayer.h b/tikzit-1/src/osx/SelectBoxLayer.h new file mode 100644 index 0000000..45b43c7 --- /dev/null +++ b/tikzit-1/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-1/src/osx/SelectBoxLayer.m b/tikzit-1/src/osx/SelectBoxLayer.m new file mode 100644 index 0000000..a7abe33 --- /dev/null +++ b/tikzit-1/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 { + if (!(self = [super init])) return nil; + 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]; +} + +@end diff --git a/tikzit-1/src/osx/SelectableCollectionViewItem.h b/tikzit-1/src/osx/SelectableCollectionViewItem.h new file mode 100644 index 0000000..4a2c571 --- /dev/null +++ b/tikzit-1/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-1/src/osx/SelectableCollectionViewItem.m b/tikzit-1/src/osx/SelectableCollectionViewItem.m new file mode 100644 index 0000000..880c37b --- /dev/null +++ b/tikzit-1/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-1/src/osx/SelectableNodeView.h b/tikzit-1/src/osx/SelectableNodeView.h new file mode 100644 index 0000000..6b0841d --- /dev/null +++ b/tikzit-1/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 (strong) NodeStyle *nodeStyle; + + +@end diff --git a/tikzit-1/src/osx/SelectableNodeView.m b/tikzit-1/src/osx/SelectableNodeView.m new file mode 100644 index 0000000..797a137 --- /dev/null +++ b/tikzit-1/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 { + if (!(self = [super initWithFrame:frameRect])) return nil; + 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-1/src/osx/StylePaletteController.h b/tikzit-1/src/osx/StylePaletteController.h new file mode 100644 index 0000000..05f0684 --- /dev/null +++ b/tikzit-1/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 (strong) NSMutableArray *nodeStyles; +@property (strong) NSMutableArray *edgeStyles; +@property (readonly) BOOL documentActive; +@property (strong) NodeStyle *activeNodeStyle; +@property (strong) 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-1/src/osx/StylePaletteController.m b/tikzit-1/src/osx/StylePaletteController.m new file mode 100644 index 0000000..4fe46be --- /dev/null +++ b/tikzit-1/src/osx/StylePaletteController.m @@ -0,0 +1,252 @@ +// +// 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]; + + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"net.sourceforge.tikzit.inspectorsontop"] == YES) { + [[self window] setLevel:NSFloatingWindowLevel]; + } else { + [[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-1/src/osx/TikzDocument.h b/tikzit-1/src/osx/TikzDocument.h new file mode 100644 index 0000000..1881994 --- /dev/null +++ b/tikzit-1/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 (weak, readonly) GraphicsView *graphicsView; + +@end diff --git a/tikzit-1/src/osx/TikzDocument.m b/tikzit-1/src/osx/TikzDocument.m new file mode 100644 index 0000000..ef5908d --- /dev/null +++ b/tikzit-1/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-1/src/osx/TikzFormatter.h b/tikzit-1/src/osx/TikzFormatter.h new file mode 100644 index 0000000..4d9ec04 --- /dev/null +++ b/tikzit-1/src/osx/TikzFormatter.h @@ -0,0 +1,29 @@ +// +// NSTikzFormatter.h +// TikZiT +// +// Created by Karl Johan Paulsson on 27/01/2013. +// Copyright (c) 2013 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 TikzFormatter : NSFormatter + +@end diff --git a/tikzit-1/src/osx/TikzFormatter.m b/tikzit-1/src/osx/TikzFormatter.m new file mode 100644 index 0000000..cb0865d --- /dev/null +++ b/tikzit-1/src/osx/TikzFormatter.m @@ -0,0 +1,91 @@ +// +// NSTikzFormatter.m +// TikZiT +// +// Created by Karl Johan Paulsson on 27/01/2013. +// Copyright (c) 2013 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 "TikzFormatter.h" +#import "TikzGraphAssembler.h" + +@implementation TikzFormatter + +- (NSString *)stringForObjectValue:(id)obj{ + if (![obj isKindOfClass:[NSString class]]) { + return @""; + } + + return [NSString stringWithString:obj]; +} + +- (BOOL)getObjectValue:(out id *)obj forString:(NSString *)string errorDescription:(out NSString **)error{ + *obj = [NSString stringWithString:string]; + + BOOL r = [TikzGraphAssembler validateTikzPropertyNameOrValue:string]; + + if (!r && error) + *error = NSLocalizedString(@"Invalid input, couldn't parse value.", @"tikz user input error"); + + return r; +} + +- (BOOL)isPartialStringValid:(NSString **)partialStringPtr proposedSelectedRange:(NSRangePointer)proposedSelRangePtr originalString:(NSString *)origString originalSelectedRange:(NSRange)origSelRange errorDescription:(NSString **)error{ + NSRange addedRange; + NSString *addedString; + + if(![[NSUserDefaults standardUserDefaults] boolForKey:@"net.sourceforge.tikzit.autocomplete"]){ + return YES; + } + + addedRange = NSMakeRange(origSelRange.location, proposedSelRangePtr->location - origSelRange.location); + addedString = [*partialStringPtr substringWithRange: addedRange]; + + if([addedString isEqualToString:@"{"]){ + NSString *s = [[NSString stringWithString:*partialStringPtr] stringByAppendingString:@"}"]; + *partialStringPtr = s; + + return NO; + } + + if([addedString isEqualToString:@"}"]){ + NSScanner *scanner = [NSScanner scannerWithString:*partialStringPtr]; + + NSCharacterSet *cs = [NSCharacterSet characterSetWithCharactersInString:@"{}"]; + NSMutableString *strippedString = [NSMutableString stringWithCapacity:[*partialStringPtr length]]; + + while ([scanner isAtEnd] == NO) { + NSString *buffer; + if ([scanner scanCharactersFromSet:cs intoString:&buffer]) { + [strippedString appendString:buffer]; + + } else { + [scanner setScanLocation:([scanner scanLocation] + 1)]; + } + } + + if([strippedString length] % 2 == 1){ + return NO; + } + } + + return YES; +} + +@end diff --git a/tikzit-1/src/osx/TikzSourceController.h b/tikzit-1/src/osx/TikzSourceController.h new file mode 100644 index 0000000..84d36da --- /dev/null +++ b/tikzit-1/src/osx/TikzSourceController.h @@ -0,0 +1,71 @@ +// +// 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 "ParseErrorView.h" + +@interface TikzSourceController : NSObject { + GraphicsView *__weak graphicsView; + NSTextView *__unsafe_unretained sourceView; + NSAttributedString *source; + NSTextField *__weak status; + NSDictionary *textAttrs; + NSColor *successColor; + NSColor *failedColor; + NSTextField *__weak errorMessage; + ParseErrorView *__weak errorNotification; + + NSUndoManager *__weak documentUndoManager; + + BOOL tikzChanged; + BOOL justUndid; + + NSError *lastError; +} + +@property BOOL tikzChanged; +@property (weak) IBOutlet GraphicsView *graphicsView; +@property (unsafe_unretained) IBOutlet NSTextView *sourceView; +@property (weak) IBOutlet NSTextField *status; +@property (weak) NSUndoManager *documentUndoManager; +@property (copy) NSAttributedString *source; +@property (copy) NSString *tikz; +@property (weak) IBOutlet ParseErrorView *errorNotification; +@property (weak) IBOutlet NSTextField *errorMessage; + +- (void)updateTikzFromGraph; +- (void)graphChanged:(NSNotification*)n; + +- (IBAction)closeParseError:(id)pId; + +// 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-1/src/osx/TikzSourceController.m b/tikzit-1/src/osx/TikzSourceController.m new file mode 100644 index 0000000..84eb3a5 --- /dev/null +++ b/tikzit-1/src/osx/TikzSourceController.m @@ -0,0 +1,241 @@ +// +// 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 "TikzGraphAssembler.h" +#import "Graph.h" + +@implementation TikzSourceController + +@synthesize graphicsView, sourceView, source, status; +@synthesize documentUndoManager, tikzChanged; +@synthesize errorMessage, errorNotification; + +- (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; + 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]]; + [errorNotification setHidden:TRUE]; +} + +- (void)graphChanged:(NSNotification*)n { + if ([graphicsView enabled]) [self updateTikzFromGraph]; +} + +- (IBAction)closeParseError:(id)pId{ + [errorNotification setHidden:TRUE]; +} + +- (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 { + NSError *thisError; + + Graph *g = [TikzGraphAssembler parseTikz:[self tikz] + error:&thisError]; + + lastError = thisError; + + if (g) { + [graphicsView deselectAll:self]; + [graphicsView setGraph:g]; + [graphicsView refreshLayers]; + [self doRevertTikz]; + return YES; + } else { + return NO; + } +} + +- (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]; + + [errorNotification setHidden:TRUE]; + } else { + [status setStringValue:@"parse error"]; + [status setTextColor:failedColor]; + + NSDictionary *d = [lastError userInfo]; + + NSString *ts = [NSString stringWithFormat: @"Parse error on line %@: %@\n", [d valueForKey:@"startLine"], [d valueForKey:NSLocalizedDescriptionKey]]; + NSMutableAttributedString *as = [[NSMutableAttributedString alloc] initWithString:[NSString stringWithFormat: @"Parse error on line %@: %@\n%@\n", [d valueForKey:@"startLine"], [d valueForKey:NSLocalizedDescriptionKey], [[d valueForKey:@"syntaxString"] stringByReplacingOccurrencesOfString:@"\t" withString:@""]]]; + + NSInteger tokenLength = [[d valueForKey:@"tokenLength"] integerValue]; + // Bit of a mess, offset around to find correct position and correct for 4 characters for every one character of \t + NSInteger addedTokenStart = [[d valueForKey:@"tokenStart"] integerValue] + [ts length] - ([[[d valueForKey:@"syntaxString"] componentsSeparatedByString:@"\t"] count]-1)*4 - tokenLength; + + // Can't see if the error is a start paranthesis as only that will be underlined, underline the entire paranthesis instead + if(tokenLength == 1 && [[as string] characterAtIndex:addedTokenStart] == '('){ + tokenLength += [[[as string] substringFromIndex:addedTokenStart+1] rangeOfString:@")"].location + 1; + } + + // Same if unexpected endparanthesis + if(tokenLength == 1 && [[as string] characterAtIndex:addedTokenStart] == ')'){ + NSInteger d = addedTokenStart - [[[as string] substringToIndex:addedTokenStart] rangeOfString:@"(" options:NSBackwardsSearch].location; + + tokenLength += d; + addedTokenStart -= d; + } + + [as beginEditing]; + [as addAttributes:[NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInt:NSUnderlineStyleSingle | NSUnderlinePatternDot], NSUnderlineStyleAttributeName, + [NSColor redColor], NSUnderlineColorAttributeName, + nil] + range:NSMakeRange(addedTokenStart, tokenLength)]; + [as endEditing]; + + [errorMessage setAttributedStringValue:as]; + [errorNotification setHidden:FALSE]; + } + } +} + +- (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)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +@end diff --git a/tikzit-1/src/osx/TikzWindowController.h b/tikzit-1/src/osx/TikzWindowController.h new file mode 100644 index 0000000..eab427c --- /dev/null +++ b/tikzit-1/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 *__weak graphicsView; + TikzSourceController *__weak tikzSourceController; + TikzDocument *document; +} + +@property (weak) IBOutlet GraphicsView *graphicsView; +@property (weak) 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-1/src/osx/TikzWindowController.m b/tikzit-1/src/osx/TikzWindowController.m new file mode 100644 index 0000000..bfacbfb --- /dev/null +++ b/tikzit-1/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 { + if (!(self = [super initWithWindowNibName:@"TikzDocument"])) return nil; + 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-1/src/osx/ToolPaletteController.h b/tikzit-1/src/osx/ToolPaletteController.h new file mode 100644 index 0000000..6301c6b --- /dev/null +++ b/tikzit-1/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 *__weak toolPalette; + NSMatrix *__weak toolMatrix; +} + +@property TikzTool selectedTool; +@property (weak) IBOutlet NSPanel *toolPalette; +@property (weak) IBOutlet NSMatrix *toolMatrix; + + +@end diff --git a/tikzit-1/src/osx/ToolPaletteController.m b/tikzit-1/src/osx/ToolPaletteController.m new file mode 100644 index 0000000..000287d --- /dev/null +++ b/tikzit-1/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-1/src/osx/UpdatePreferenceController.h b/tikzit-1/src/osx/UpdatePreferenceController.h new file mode 100644 index 0000000..816322f --- /dev/null +++ b/tikzit-1/src/osx/UpdatePreferenceController.h @@ -0,0 +1,34 @@ +// +// UpdatePreferenceController.h +// TikZiT +// +// Copyright (c) 2013 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 <Sparkle/Sparkle.h> + +@interface UpdatePreferenceController : NSViewController{ + IBOutlet SUUpdater *sharedUpdater; + IBOutlet NSDate *lastUpdate; +} + +- (IBAction)checkForUpdates:(id)sender; + +@end diff --git a/tikzit-1/src/osx/UpdatePreferenceController.m b/tikzit-1/src/osx/UpdatePreferenceController.m new file mode 100644 index 0000000..2ff270f --- /dev/null +++ b/tikzit-1/src/osx/UpdatePreferenceController.m @@ -0,0 +1,49 @@ +// +// UpdatePreferenceController.h +// TikZiT +// +// Copyright (c) 2013 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 "UpdatePreferenceController.h" + +@interface UpdatePreferenceController () + +@end + +@implementation UpdatePreferenceController + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil +{ + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + sharedUpdater = [SUUpdater sharedUpdater]; + } + return self; +} + +- (IBAction)checkForUpdates:(id)sender{ + [sharedUpdater checkForUpdates:sender]; +} + +- (NSDate*)getLastUpdate{ + return [sharedUpdater lastUpdateCheckDate]; +} + +@end diff --git a/tikzit-1/src/osx/UpdatePreferencePanel.xib b/tikzit-1/src/osx/UpdatePreferencePanel.xib new file mode 100644 index 0000000..a9f57bd --- /dev/null +++ b/tikzit-1/src/osx/UpdatePreferencePanel.xib @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="4514" systemVersion="13A603" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> + <dependencies> + <deployment defaultVersion="1070" identifier="macosx"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="4514"/> + </dependencies> + <objects> + <customObject id="-2" userLabel="File's Owner" customClass="UpdatePreferenceController"> + <connections> + <outlet property="view" destination="Rvg-HP-aJv" id="BaV-kv-ixT"/> + </connections> + </customObject> + <customObject id="QYw-wO-Hk1" customClass="SUUpdater"/> + <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/> + <customObject id="-3" userLabel="Application"/> + <customView autoresizesSubviews="NO" id="Rvg-HP-aJv"> + <rect key="frame" x="0.0" y="0.0" width="480" height="118"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <subviews> + <button translatesAutoresizingMaskIntoConstraints="NO" id="r97-6h-YHZ"> + <rect key="frame" x="18" y="82" width="225" height="18"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="check" title="Automatically check for updates" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="lJX-eh-125"> + <behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="checkForUpdates:" target="QYw-wO-Hk1" id="V6O-vN-gzU"/> + </connections> + </button> + <popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="rTe-Zb-gRB"> + <rect key="frame" x="247" y="77" width="75" height="26"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <constraints> + <constraint firstAttribute="width" constant="70" id="E5t-Vq-EsW"/> + </constraints> + <popUpButtonCell key="cell" type="push" title="Hourly" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="3600" imageScaling="proportionallyDown" inset="2" id="1sH-Qj-rJY"> + <behavior key="behavior" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + <menu key="menu" title="OtherViews" id="Thn-kJ-wMh"> + <items> + <menuItem title="Hourly" state="on" tag="3600" id="pfB-GR-wmt"/> + <menuItem title="Daily" tag="86400" id="i4M-UZ-VYz"/> + <menuItem title="Weekly" tag="604800" id="xUT-n5-NQ6"/> + <menuItem title="Monthly" tag="2629800" id="6fb-Ll-N0L"/> + </items> + </menu> + </popUpButtonCell> + <connections> + <binding destination="QYw-wO-Hk1" name="enabled" keyPath="automaticallyChecksForUpdates" id="MEk-e1-13l"/> + <binding destination="QYw-wO-Hk1" name="selectedTag" keyPath="updateCheckInterval" id="ZgU-vh-YId"/> + </connections> + </popUpButton> + <button verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Edp-7i-rVO"> + <rect key="frame" x="151" y="40" width="179" height="32"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <buttonCell key="cell" type="push" title="Check for update now" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6TE-6d-y5s"> + <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> + <font key="font" metaFont="system"/> + </buttonCell> + <connections> + <action selector="checkForUpdates:" target="-2" id="3kl-5L-9j6"/> + </connections> + </button> + <textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kog-eQ-eh0"> + <rect key="frame" x="18" y="20" width="444" height="14"/> + <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/> + <textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="Label" id="yBa-Dx-WK4"> + <font key="font" size="11" name="LucidaGrande"/> + <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> + <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> + <connections> + <binding destination="-2" name="value" keyPath="lastUpdate" id="ULc-O0-aZL"> + <dictionary key="options"> + <bool key="NSRaisesForNotApplicableKeys" value="NO"/> + <bool key="NSAllowsEditingMultipleValuesSelection" value="NO"/> + </dictionary> + </binding> + </connections> + </textFieldCell> + <connections> + <outlet property="formatter" destination="N9w-eT-NdL" id="Yn2-zV-GqY"/> + </connections> + </textField> + </subviews> + <constraints> + <constraint firstItem="r97-6h-YHZ" firstAttribute="top" secondItem="Rvg-HP-aJv" secondAttribute="top" constant="20" symbolic="YES" id="5PN-8T-E7N"/> + <constraint firstItem="rTe-Zb-gRB" firstAttribute="leading" secondItem="r97-6h-YHZ" secondAttribute="trailing" constant="8" symbolic="YES" id="JEG-EP-kMS"/> + <constraint firstItem="rTe-Zb-gRB" firstAttribute="baseline" secondItem="r97-6h-YHZ" secondAttribute="baseline" id="TAn-67-9cC"/> + <constraint firstItem="r97-6h-YHZ" firstAttribute="leading" secondItem="Rvg-HP-aJv" secondAttribute="leading" constant="20" symbolic="YES" id="o4v-st-ibG"/> + </constraints> + </customView> + <dateFormatter dateStyle="short" timeStyle="short" doesRelativeDateFormatting="YES" id="N9w-eT-NdL"/> + </objects> +</document>
\ No newline at end of file diff --git a/tikzit-1/src/osx/main.m b/tikzit-1/src/osx/main.m new file mode 100644 index 0000000..e6b4499 --- /dev/null +++ b/tikzit-1/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-1/src/osx/test/main.m b/tikzit-1/src/osx/test/main.m new file mode 100644 index 0000000..ad0c1f7 --- /dev/null +++ b/tikzit-1/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-1/src/osx/test/osx.m b/tikzit-1/src/osx/test/osx.m new file mode 100644 index 0000000..f9565ab --- /dev/null +++ b/tikzit-1/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-1/src/tikzit.rc b/tikzit-1/src/tikzit.rc new file mode 100644 index 0000000..072f825 --- /dev/null +++ b/tikzit-1/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" |