summaryrefslogtreecommitdiff
path: root/tikzit-1/src
diff options
context:
space:
mode:
authorAleks Kissinger <aleks0@gmail.com>2017-01-11 16:33:00 +0100
committerAleks Kissinger <aleks0@gmail.com>2017-01-11 16:33:00 +0100
commit1802977b95d29198f27535b1b731d1180c083667 (patch)
tree032c4beb7411d88d76794a25f0e3b00a3437da3e /tikzit-1/src
parentff79a9c213dfd75ea00ed5112d3a6e314601e064 (diff)
made new subdir
Diffstat (limited to 'tikzit-1/src')
-rw-r--r--tikzit-1/src/Makefile.am178
-rw-r--r--tikzit-1/src/common/CircleShape.h33
-rw-r--r--tikzit-1/src/common/CircleShape.m57
-rw-r--r--tikzit-1/src/common/ColorRGB.h64
-rw-r--r--tikzit-1/src/common/ColorRGB.m353
-rw-r--r--tikzit-1/src/common/DiamondShape.h34
-rw-r--r--tikzit-1/src/common/DiamondShape.m63
-rw-r--r--tikzit-1/src/common/Edge.h401
-rw-r--r--tikzit-1/src/common/Edge.m757
-rw-r--r--tikzit-1/src/common/EdgeStyle.h71
-rw-r--r--tikzit-1/src/common/EdgeStyle.m222
-rw-r--r--tikzit-1/src/common/Graph.h401
-rw-r--r--tikzit-1/src/common/Graph.m922
-rw-r--r--tikzit-1/src/common/GraphChange.h344
-rw-r--r--tikzit-1/src/common/GraphChange.m369
-rw-r--r--tikzit-1/src/common/GraphElementData.h94
-rw-r--r--tikzit-1/src/common/GraphElementData.m188
-rw-r--r--tikzit-1/src/common/GraphElementProperty.h88
-rw-r--r--tikzit-1/src/common/GraphElementProperty.m164
-rw-r--r--tikzit-1/src/common/Grid.h110
-rw-r--r--tikzit-1/src/common/Grid.m186
-rw-r--r--tikzit-1/src/common/NSError+Tikzit.h44
-rw-r--r--tikzit-1/src/common/NSError+Tikzit.m64
-rw-r--r--tikzit-1/src/common/NSFileManager+Utils.h29
-rw-r--r--tikzit-1/src/common/NSFileManager+Utils.m46
-rw-r--r--tikzit-1/src/common/NSString+LatexConstants.h33
-rw-r--r--tikzit-1/src/common/NSString+LatexConstants.m212
-rw-r--r--tikzit-1/src/common/NSString+Tikz.h26
-rw-r--r--tikzit-1/src/common/NSString+Tikz.m72
-rw-r--r--tikzit-1/src/common/NSString+Util.h27
-rw-r--r--tikzit-1/src/common/NSString+Util.m66
-rw-r--r--tikzit-1/src/common/Node.h181
-rw-r--r--tikzit-1/src/common/Node.m214
-rw-r--r--tikzit-1/src/common/NodeStyle.h125
-rw-r--r--tikzit-1/src/common/NodeStyle.m246
-rw-r--r--tikzit-1/src/common/PickSupport.h164
-rw-r--r--tikzit-1/src/common/PickSupport.m232
-rw-r--r--tikzit-1/src/common/Preambles.h73
-rw-r--r--tikzit-1/src/common/Preambles.m320
-rw-r--r--tikzit-1/src/common/PropertyHolder.h36
-rw-r--r--tikzit-1/src/common/PropertyHolder.m74
-rw-r--r--tikzit-1/src/common/RColor.h50
-rw-r--r--tikzit-1/src/common/RColor.m33
-rw-r--r--tikzit-1/src/common/RectangleShape.h33
-rw-r--r--tikzit-1/src/common/RectangleShape.m57
-rw-r--r--tikzit-1/src/common/RegularPolyShape.h50
-rw-r--r--tikzit-1/src/common/RegularPolyShape.m76
-rw-r--r--tikzit-1/src/common/RenderContext.h156
-rw-r--r--tikzit-1/src/common/Shape.h49
-rw-r--r--tikzit-1/src/common/Shape.m171
-rw-r--r--tikzit-1/src/common/ShapeNames.h27
-rw-r--r--tikzit-1/src/common/StyleManager.h49
-rw-r--r--tikzit-1/src/common/StyleManager.m378
-rw-r--r--tikzit-1/src/common/SupportDir.h36
-rw-r--r--tikzit-1/src/common/SupportDir.m65
-rw-r--r--tikzit-1/src/common/TikzGraphAssembler+Parser.h36
-rw-r--r--tikzit-1/src/common/TikzGraphAssembler.h115
-rw-r--r--tikzit-1/src/common/TikzGraphAssembler.m310
-rw-r--r--tikzit-1/src/common/TikzShape.h37
-rw-r--r--tikzit-1/src/common/TikzShape.m70
-rw-r--r--tikzit-1/src/common/Transformer.h154
-rw-r--r--tikzit-1/src/common/Transformer.m231
-rw-r--r--tikzit-1/src/common/test/Makefile14
-rw-r--r--tikzit-1/src/common/test/color.m80
-rw-r--r--tikzit-1/src/common/test/common.m34
-rw-r--r--tikzit-1/src/common/test/maths.m562
-rw-r--r--tikzit-1/src/common/test/parser.m86
-rw-r--r--tikzit-1/src/common/test/test.h57
-rw-r--r--tikzit-1/src/common/test/test.m175
-rw-r--r--tikzit-1/src/common/tikzlexer.lm170
-rw-r--r--tikzit-1/src/common/tikzparser.ym224
-rw-r--r--tikzit-1/src/common/tikzparserdefs.h49
-rw-r--r--tikzit-1/src/common/util.h201
-rw-r--r--tikzit-1/src/common/util.m403
-rw-r--r--tikzit-1/src/gtk/Application.h155
-rw-r--r--tikzit-1/src/gtk/Application.m377
-rw-r--r--tikzit-1/src/gtk/BoundingBoxTool.h45
-rw-r--r--tikzit-1/src/gtk/BoundingBoxTool.m353
-rw-r--r--tikzit-1/src/gtk/CairoRenderContext.h59
-rw-r--r--tikzit-1/src/gtk/CairoRenderContext.m344
-rw-r--r--tikzit-1/src/gtk/ColorRGB+Gtk.h30
-rw-r--r--tikzit-1/src/gtk/ColorRGB+Gtk.m46
-rw-r--r--tikzit-1/src/gtk/ColorRGB+IntegerListStorage.h32
-rw-r--r--tikzit-1/src/gtk/ColorRGB+IntegerListStorage.m57
-rw-r--r--tikzit-1/src/gtk/Configuration.h447
-rw-r--r--tikzit-1/src/gtk/Configuration.m450
-rw-r--r--tikzit-1/src/gtk/ContextWindow.h53
-rw-r--r--tikzit-1/src/gtk/ContextWindow.m169
-rw-r--r--tikzit-1/src/gtk/CreateEdgeTool.h45
-rw-r--r--tikzit-1/src/gtk/CreateEdgeTool.m226
-rw-r--r--tikzit-1/src/gtk/CreateNodeTool.h42
-rw-r--r--tikzit-1/src/gtk/CreateNodeTool.m169
-rw-r--r--tikzit-1/src/gtk/DocumentContext.h27
-rw-r--r--tikzit-1/src/gtk/Edge+Render.h34
-rw-r--r--tikzit-1/src/gtk/Edge+Render.m267
-rw-r--r--tikzit-1/src/gtk/EdgeStyle+Gtk.h29
-rw-r--r--tikzit-1/src/gtk/EdgeStyle+Gtk.m33
-rw-r--r--tikzit-1/src/gtk/EdgeStyle+Storage.h29
-rw-r--r--tikzit-1/src/gtk/EdgeStyle+Storage.m55
-rw-r--r--tikzit-1/src/gtk/EdgeStyleEditor.h45
-rw-r--r--tikzit-1/src/gtk/EdgeStyleEditor.m499
-rw-r--r--tikzit-1/src/gtk/EdgeStyleSelector.h61
-rw-r--r--tikzit-1/src/gtk/EdgeStyleSelector.m143
-rw-r--r--tikzit-1/src/gtk/EdgeStylesModel.h62
-rw-r--r--tikzit-1/src/gtk/EdgeStylesModel.m367
-rw-r--r--tikzit-1/src/gtk/EdgeStylesPalette.h43
-rw-r--r--tikzit-1/src/gtk/EdgeStylesPalette.m198
-rw-r--r--tikzit-1/src/gtk/FileChooserDialog.h56
-rw-r--r--tikzit-1/src/gtk/FileChooserDialog.m152
-rw-r--r--tikzit-1/src/gtk/GraphEditorPanel.h53
-rw-r--r--tikzit-1/src/gtk/GraphEditorPanel.m240
-rw-r--r--tikzit-1/src/gtk/GraphRenderer.h84
-rw-r--r--tikzit-1/src/gtk/GraphRenderer.m476
-rw-r--r--tikzit-1/src/gtk/HandTool.h33
-rw-r--r--tikzit-1/src/gtk/HandTool.m75
-rw-r--r--tikzit-1/src/gtk/InputDelegate.h77
-rw-r--r--tikzit-1/src/gtk/Menu.h86
-rw-r--r--tikzit-1/src/gtk/Menu.m737
-rw-r--r--tikzit-1/src/gtk/NSError+Glib.h28
-rw-r--r--tikzit-1/src/gtk/NSError+Glib.m50
-rw-r--r--tikzit-1/src/gtk/NSFileManager+Glib.h31
-rw-r--r--tikzit-1/src/gtk/NSFileManager+Glib.m55
-rw-r--r--tikzit-1/src/gtk/NSString+Glib.h50
-rw-r--r--tikzit-1/src/gtk/NSString+Glib.m96
-rw-r--r--tikzit-1/src/gtk/Node+Render.h44
-rw-r--r--tikzit-1/src/gtk/Node+Render.m188
-rw-r--r--tikzit-1/src/gtk/NodeStyle+Gtk.h31
-rw-r--r--tikzit-1/src/gtk/NodeStyle+Gtk.m41
-rw-r--r--tikzit-1/src/gtk/NodeStyle+Render.h30
-rw-r--r--tikzit-1/src/gtk/NodeStyle+Storage.h29
-rw-r--r--tikzit-1/src/gtk/NodeStyle+Storage.m62
-rw-r--r--tikzit-1/src/gtk/NodeStyleEditor.h45
-rw-r--r--tikzit-1/src/gtk/NodeStyleEditor.m477
-rw-r--r--tikzit-1/src/gtk/NodeStyleSelector.h61
-rw-r--r--tikzit-1/src/gtk/NodeStyleSelector.m135
-rw-r--r--tikzit-1/src/gtk/NodeStylesModel.h62
-rw-r--r--tikzit-1/src/gtk/NodeStylesModel.m381
-rw-r--r--tikzit-1/src/gtk/NodeStylesPalette.h43
-rw-r--r--tikzit-1/src/gtk/NodeStylesPalette.m197
-rw-r--r--tikzit-1/src/gtk/PreambleEditor.h51
-rw-r--r--tikzit-1/src/gtk/PreambleEditor.m568
-rw-r--r--tikzit-1/src/gtk/Preambles+Storage.h29
-rw-r--r--tikzit-1/src/gtk/Preambles+Storage.m84
-rw-r--r--tikzit-1/src/gtk/PreviewRenderer.h48
-rw-r--r--tikzit-1/src/gtk/PreviewRenderer.m250
-rw-r--r--tikzit-1/src/gtk/PreviewWindow.h51
-rw-r--r--tikzit-1/src/gtk/PreviewWindow.m195
-rw-r--r--tikzit-1/src/gtk/PropertiesPane.h69
-rw-r--r--tikzit-1/src/gtk/PropertiesPane.m763
-rw-r--r--tikzit-1/src/gtk/PropertyListEditor.h65
-rw-r--r--tikzit-1/src/gtk/PropertyListEditor.m501
-rw-r--r--tikzit-1/src/gtk/RecentManager.h30
-rw-r--r--tikzit-1/src/gtk/RecentManager.m74
-rw-r--r--tikzit-1/src/gtk/SelectTool.h63
-rw-r--r--tikzit-1/src/gtk/SelectTool.m590
-rw-r--r--tikzit-1/src/gtk/SelectionPane.h56
-rw-r--r--tikzit-1/src/gtk/SelectionPane.m432
-rw-r--r--tikzit-1/src/gtk/SettingsDialog.h54
-rw-r--r--tikzit-1/src/gtk/SettingsDialog.m328
-rw-r--r--tikzit-1/src/gtk/Shape+Render.h29
-rw-r--r--tikzit-1/src/gtk/Shape+Render.m57
-rw-r--r--tikzit-1/src/gtk/StyleManager+Storage.h26
-rw-r--r--tikzit-1/src/gtk/StyleManager+Storage.m82
-rw-r--r--tikzit-1/src/gtk/Surface.h107
-rw-r--r--tikzit-1/src/gtk/TZFoundation.h30
-rw-r--r--tikzit-1/src/gtk/TikzDocument.h149
-rw-r--r--tikzit-1/src/gtk/TikzDocument.m911
-rw-r--r--tikzit-1/src/gtk/Tool.h41
-rw-r--r--tikzit-1/src/gtk/ToolBox.h45
-rw-r--r--tikzit-1/src/gtk/ToolBox.m280
-rw-r--r--tikzit-1/src/gtk/WidgetSurface.h54
-rw-r--r--tikzit-1/src/gtk/WidgetSurface.m630
-rw-r--r--tikzit-1/src/gtk/Window.h182
-rw-r--r--tikzit-1/src/gtk/Window.m991
-rw-r--r--tikzit-1/src/gtk/cairo_helpers.h25
-rw-r--r--tikzit-1/src/gtk/cairo_helpers.m28
-rw-r--r--tikzit-1/src/gtk/clipboard.h41
-rw-r--r--tikzit-1/src/gtk/clipboard.m57
-rw-r--r--tikzit-1/src/gtk/gtkhelpers.h60
-rw-r--r--tikzit-1/src/gtk/gtkhelpers.m275
-rw-r--r--tikzit-1/src/gtk/logo.h32
-rw-r--r--tikzit-1/src/gtk/logo.m64
-rw-r--r--tikzit-1/src/gtk/main.m111
-rw-r--r--tikzit-1/src/gtk/mkdtemp.h32
-rw-r--r--tikzit-1/src/gtk/mkdtemp.m180
-rw-r--r--tikzit-1/src/gtk/stat.h25
-rw-r--r--tikzit-1/src/gtk/test/gtk.m27
-rw-r--r--tikzit-1/src/gtk/test/main.m50
-rw-r--r--tikzit-1/src/gtk/tzstockitems.h26
-rw-r--r--tikzit-1/src/gtk/tzstockitems.m64
-rw-r--r--tikzit-1/src/gtk/tztoolpalette.h56
-rw-r--r--tikzit-1/src/gtk/tztoolpalette.m158
-rw-r--r--tikzit-1/src/osx/AppDelegate.h57
-rw-r--r--tikzit-1/src/osx/AppDelegate.m124
-rw-r--r--tikzit-1/src/osx/CALayer+DrawLabel.h21
-rw-r--r--tikzit-1/src/osx/CALayer+DrawLabel.m84
-rw-r--r--tikzit-1/src/osx/CoreGraphicsRenderContext.h44
-rw-r--r--tikzit-1/src/osx/CoreGraphicsRenderContext.m234
-rw-r--r--tikzit-1/src/osx/CustomNodeCellView.h23
-rw-r--r--tikzit-1/src/osx/CustomNodeCellView.m83
-rw-r--r--tikzit-1/src/osx/CustomNodeController.h35
-rw-r--r--tikzit-1/src/osx/CustomNodeController.m58
-rw-r--r--tikzit-1/src/osx/CustomNodes.xib249
-rw-r--r--tikzit-1/src/osx/DraggablePDFView.h28
-rw-r--r--tikzit-1/src/osx/DraggablePDFView.m60
-rw-r--r--tikzit-1/src/osx/EdgeControlLayer.h44
-rw-r--r--tikzit-1/src/osx/EdgeControlLayer.m150
-rw-r--r--tikzit-1/src/osx/EdgeStyle+Coder.h30
-rw-r--r--tikzit-1/src/osx/EdgeStyle+Coder.m50
-rw-r--r--tikzit-1/src/osx/Graph+Coder.h17
-rw-r--r--tikzit-1/src/osx/Graph+Coder.m24
-rw-r--r--tikzit-1/src/osx/GraphicsView.h129
-rw-r--r--tikzit-1/src/osx/GraphicsView.m1216
-rw-r--r--tikzit-1/src/osx/Grid.h48
-rw-r--r--tikzit-1/src/osx/Grid.m152
-rw-r--r--tikzit-1/src/osx/MultiCombo.h18
-rw-r--r--tikzit-1/src/osx/MultiCombo.m38
-rw-r--r--tikzit-1/src/osx/MultiField.h18
-rw-r--r--tikzit-1/src/osx/MultiField.m53
-rw-r--r--tikzit-1/src/osx/NilToEmptyStringTransformer.h28
-rw-r--r--tikzit-1/src/osx/NilToEmptyStringTransformer.m53
-rw-r--r--tikzit-1/src/osx/NodeLayer.h62
-rw-r--r--tikzit-1/src/osx/NodeLayer.m238
-rw-r--r--tikzit-1/src/osx/NodeSelectionLayer.h45
-rw-r--r--tikzit-1/src/osx/NodeSelectionLayer.m93
-rw-r--r--tikzit-1/src/osx/NodeStyle+Coder.h36
-rw-r--r--tikzit-1/src/osx/NodeStyle+Coder.m91
-rw-r--r--tikzit-1/src/osx/ParseErrorView.h13
-rw-r--r--tikzit-1/src/osx/ParseErrorView.m40
-rw-r--r--tikzit-1/src/osx/PreambleController.h57
-rw-r--r--tikzit-1/src/osx/PreambleController.m168
-rw-r--r--tikzit-1/src/osx/Preambles+Coder.h32
-rw-r--r--tikzit-1/src/osx/Preambles+Coder.m41
-rw-r--r--tikzit-1/src/osx/PreferenceController.h49
-rw-r--r--tikzit-1/src/osx/PreferenceController.m133
-rw-r--r--tikzit-1/src/osx/Preferences.xib1165
-rw-r--r--tikzit-1/src/osx/PreviewController.h52
-rw-r--r--tikzit-1/src/osx/PreviewController.m147
-rw-r--r--tikzit-1/src/osx/PropertyInspectorController.h83
-rw-r--r--tikzit-1/src/osx/PropertyInspectorController.m280
-rw-r--r--tikzit-1/src/osx/SelectBoxLayer.h22
-rw-r--r--tikzit-1/src/osx/SelectBoxLayer.m48
-rw-r--r--tikzit-1/src/osx/SelectableCollectionViewItem.h33
-rw-r--r--tikzit-1/src/osx/SelectableCollectionViewItem.m54
-rw-r--r--tikzit-1/src/osx/SelectableNodeView.h38
-rw-r--r--tikzit-1/src/osx/SelectableNodeView.m96
-rw-r--r--tikzit-1/src/osx/StylePaletteController.h80
-rw-r--r--tikzit-1/src/osx/StylePaletteController.m252
-rw-r--r--tikzit-1/src/osx/TikzDocument.h37
-rw-r--r--tikzit-1/src/osx/TikzDocument.m84
-rw-r--r--tikzit-1/src/osx/TikzFormatter.h29
-rw-r--r--tikzit-1/src/osx/TikzFormatter.m91
-rw-r--r--tikzit-1/src/osx/TikzSourceController.h71
-rw-r--r--tikzit-1/src/osx/TikzSourceController.m241
-rw-r--r--tikzit-1/src/osx/TikzWindowController.h31
-rw-r--r--tikzit-1/src/osx/TikzWindowController.m66
-rw-r--r--tikzit-1/src/osx/ToolPaletteController.h42
-rw-r--r--tikzit-1/src/osx/ToolPaletteController.m58
-rw-r--r--tikzit-1/src/osx/UpdatePreferenceController.h34
-rw-r--r--tikzit-1/src/osx/UpdatePreferenceController.m49
-rw-r--r--tikzit-1/src/osx/UpdatePreferencePanel.xib95
-rw-r--r--tikzit-1/src/osx/main.m26
-rw-r--r--tikzit-1/src/osx/test/main.m56
-rw-r--r--tikzit-1/src/osx/test/osx.m64
-rw-r--r--tikzit-1/src/tikzit.rc24
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"