Anonymous avatar Anonymous committed ab38556

Added current contents of BranchLine project

Comments (0)

Files changed (110)

Binary file added.

+/*
+ *  BLDefines.h
+ *  BranchLine
+ *
+ *  Created by Brent Gulanowski on 22/02/08.
+ *  Copyright 2008 Bored Astronaut. All rights reserved.
+ *
+ */
+
+
+#define PAGE_WIDTH 612.0f
+#define PAGE_HEIGHT 792.0f
+
+#define PAGE_MARGIN 36.0f
+#define PAGE_TITLE_BUFFER 24.0f
+
+#define SYMBOL_WIDTH 96.0f
+#define SYMBOL_HEIGHT 48.0f
+
+#define SYMBOL_HALF_WIDTH 48.0f
+#define SYMBOL_HALF_HEIGHT 24.0f
+#define SYMBOL_QUARTER_WIDTH 24.0f
+#define SYMBOL_UNIT_LENGTH_VERTICAL 12.0f
+#define SYMBOL_UNIT_LENGTH_HORIZONTAL 12.0f
+
+#define SYMBOL_SPACING_HORIZONTAL 15.0f
+#define SYMBOL_SPACING_VERTICAL 24.0f
+
+#define SYMBOL_STROKE_WIDTH 4.0f
+
+#define SYMBOL_COLOR_ALPHA  0.25f
+#define SYMBOL_COLOR_BRIGHTNESS 0.9f
+#define SYMBOL_COLOR_SATURATION 0.2f
+
+#define SYMBOL_HUE_DECISION 5.0/6.0f
+#define SYMBOL_HUE_DISPLAY 0.25f
+#define SYMBOL_HUE_END 0.0f
+#define SYMBOL_HUE_IO 1.0f/6.0f
+#define SYMBOL_HUE_JUMPLABEL 1.0f/12.0f
+#define SYMBOL_HUE_MANUALINPUT 2.0f/3.0f
+#define SYMBOL_HUE_MANUALPROCESS 0.75f
+#define SYMBOL_HUE_OFFPAGE 11.0f/12.0f
+#define SYMBOL_HUE_PREDEFINED 0.5f
+#define SYMBOL_HUE_PROCESS 7.0f/12.0f
+#define SYMBOL_HUE_START 1.0f/3.0f
+
+enum {
+	BLGridStyleLines,
+	BLGridStyleBoxes
+};
+
+typedef NSUInteger BLGridStyle;
+//
+//  BLDelegate.h
+//  BranchLine
+//
+//  Created by Brent Gulanowski on 30/03/08.
+//  Copyright 2008 Marketcircle, Inc. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+@interface BLDelegate : NSObject {
+
+	NSWindowController *prefs;
+}
+
+- (IBAction)showPreferences:(id)sender;
+
+@end
+//
+//  BLDelegate.m
+//  BranchLine
+//
+//  Created by Brent Gulanowski on 30/03/08.
+//  Copyright 2008 Marketcircle, Inc. All rights reserved.
+//
+
+#import "BLDelegate.h"
+
+
+@implementation BLDelegate
+
++ (void)initialize {
+	
+	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+	NSDictionary *values = [NSDictionary dictionaryWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"defaults" ofType:@"plist"]];
+	
+	[defaults registerDefaults:values];
+}
+
+- (void)dealloc {
+	[prefs release];
+	[super dealloc];
+}
+
+- (IBAction)showPreferences:(id)sender {
+	if(nil == prefs) {
+		prefs = [[NSWindowController alloc] initWithWindowNibName:@"BLPreferences"];
+	}
+	[[prefs window] makeKeyAndOrderFront:self];
+}
+
+@end
+//
+//  BLDiagramView.h
+//  BranchLine
+//
+//  Created by Brent Gulanowski on 14/02/08.
+//  Copyright 2008 Bored Astronaut. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+#import "BLDefines.h"
+
+
+/*
+ Page size of 8.5x11 = 612p x 792p (72 points/inch)
+ */
+
+@class BLDocument, BLNodeView, BLNode;
+
+@interface BLDiagramView : NSView {
+
+	IBOutlet BLDocument *document;
+	IBOutlet NSArrayController *nodesAC;
+	IBOutlet NSArrayController *edgesAC;
+	
+	CGFloat pageHeight;
+	NSUInteger pagesHigh;
+	CGFloat pageWidth;
+	NSUInteger pagesWide;
+	
+	CGFloat rowHeight;
+	NSUInteger maxRow;
+	CGFloat columnWidth;
+	NSUInteger maxColumn;
+	
+	CGFloat leftMargin;
+	CGFloat rightMargin;
+	CGFloat topMargin;
+	CGFloat bottomMargin;
+	
+	CGFloat hPadding;
+	CGFloat vPadding;
+		
+	NSRect interiorRect;
+	
+	NSBezierPath *gridPath;
+	NSMutableDictionary *nodeViewBindings; // subviews keyed to nodes
+	NSMutableDictionary *bindingsInfo;
+	
+	NSPoint addLocation; // in grid coordinates
+	BOOL isDraggingConnection;
+	NSPoint connectStartLocation;
+	NSPoint connectEndLocation;
+	
+	BLNodeView *selectedNodeView;	
+	BLNode *draggingNode;	
+	BLNode *destinationConnectionNode;
+	
+	// observed defaults
+	BOOL drawGrid;
+	BOOL drawMargins;
+	BLGridStyle gridStyle;
+}
+
+@property (readwrite) CGFloat pageHeight;
+@property (readwrite) NSUInteger pagesHigh;
+@property (readwrite) CGFloat pageWidth;
+@property (readwrite) NSUInteger pagesWide;
+@property (readwrite) CGFloat topMargin;
+@property (readwrite) CGFloat bottomMargin;
+@property (readwrite) CGFloat leftMargin;
+@property (readwrite) CGFloat rightMargin;
+@property (readwrite) CGFloat rowHeight;
+@property (readwrite) NSUInteger maxRow;
+@property (readwrite) CGFloat columnWidth;
+@property (readwrite) NSUInteger maxColumn;
+@property (readwrite) CGFloat hPadding;
+@property (readwrite) CGFloat vPadding;
+@property (readwrite, copy) NSBezierPath *gridPath;
+@property (readwrite, retain) NSMutableDictionary *nodeViewBindings;
+@property (readwrite, retain) BLNodeView *selectedNodeView;
+
+- (void)setAllMargins:(CGFloat)margin;
+
+- (void)selectNode:(BLNode *)aNode;
+
+- (void)updateNodeViewForChangedNodeLocation:(BLNodeView *)aNodeView;
+
+- (void)dragConnection:(NSEvent *)theEvent fromNode:(BLNode *)startNode point:(NSPoint)aPoint;
+- (void)makeConnection:(NSEvent *)theEvent fromNode:(BLNode *)startNode;
+
+- (void)makeAltConnection:(NSEvent *)theEvent fromNode:(BLNode *)startNode;
+
+- (void)updatePageLayoutWithPrintInfo:(NSPrintInfo *)printInfo;
+@end
+//
+//  BLDiagramView.m
+//  BranchLine
+//
+//  Created by Brent Gulanowski on 14/02/08.
+//  Copyright 2008 Bored Astronaut. All rights reserved.
+//
+
+#import "BLDiagramView.h"
+#import "BLNodeView.h"
+#import "BLNode.h"
+#import "BLEdge.h"
+#import "BLDocument.h"
+
+#import <AppKit/NSGraphics.h>
+#import <CoreData/CoreData.h>
+
+
+
+@interface BLDiagramView ()
+- (void)_updateGridPath;
+- (void)_drawSelectionHighlight;
+- (void)_drawBackgroundInRect:(NSRect)rect;
+- (void)_drawMargins;
+- (void)_drawBoxStyleGrid;
+- (void)_drawLineStyleGrid;
+- (void)_drawConnections;
+- (void)_observeChangeToNodesBinding:(NSDictionary *)change object:(id)object;
+- (void)_observeChangeToEdgesBinding:(NSDictionary *)change object:(id)object;
+- (void)_deleteViewsforRemovedNodes:(NSArray *)removedNodes;
+- (void)_createViewsForAddedNodes:(NSArray *)addedNodes;
+- (NSRect)_frameForNode:(BLNode *)aNode;
+- (BLNodeView *)_newViewForNode:(BLNode *)aNode;
+- (BLNodeView *)_newViewForNode:(BLNode *)aNode withFrame:(NSRect)frame;
+- (NSPoint)_closestLocationToPoint:(NSPoint)aPoint;
+@end
+
+
+
+
+@implementation BLDiagramView
+
+@synthesize pageWidth, pagesWide, pageHeight, pagesHigh;
+@synthesize topMargin, bottomMargin, leftMargin, rightMargin;
+@synthesize rowHeight, maxRow, columnWidth, maxColumn;
+@synthesize vPadding, hPadding;
+@synthesize gridPath, nodeViewBindings, selectedNodeView;
+
+
+NSColor *marginColor;
+NSColor *gridLineColor;
+NSColor *gridFillColor;
+NSColor *connectionLineColor;
+NSDictionary *bindingsSymbols;
+NSBezierPath *arrowHeadPath;
+
+
+#pragma mark NSObject
++ (void)initialize {
+	
+	if( ! ([self class] == [BLDiagramView class]) )
+		return;
+	
+	marginColor = [[NSColor colorWithCalibratedRed:1.0f green:0.0f blue:0.0f alpha:0.1f] retain];
+	gridLineColor = [[NSColor colorWithCalibratedRed:0.4f green:0.8f blue:0.8f alpha:0.1f] retain];
+	gridFillColor = [[NSColor colorWithCalibratedRed:0.8f green:0.8f blue:0.8f alpha:0.1f] retain];
+	connectionLineColor = [[NSColor colorWithCalibratedRed:0.7f green:0.7f blue:0.7f alpha:1.0f] retain];
+	
+	bindingsSymbols = [[NSDictionary dictionaryWithObjectsAndKeys:
+					   @"Nodes", @"nodes",
+					   @"Edges", @"edges",
+					   nil] retain];
+	
+	arrowHeadPath = [[NSBezierPath alloc] init];
+	[arrowHeadPath moveToPoint:NSMakePoint(0.0f, -2.0f)];
+	[arrowHeadPath lineToPoint:NSMakePoint(6.0f, -12.0f)];
+	[arrowHeadPath lineToPoint:NSMakePoint(-6.0f, -12.0f)];
+	[arrowHeadPath closePath];
+}
+
+- (void)dealloc {
+	
+	NSLog(@"DiagramView got dealloc: %@", self);
+	
+	[[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:@"drawsMargins"];
+	[[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:@"drawsGrid"];
+
+	[self unbind:@"nodes"];
+	[self unbind:@"edges"];
+
+	for(NSString *key in [self.nodeViewBindings allKeys]) {
+		BLNodeView *view = [self.nodeViewBindings objectForKey:key];
+		[view unbind:@"node"];
+		[view removeFromSuperview];
+		[self.nodeViewBindings removeObjectForKey:key];
+	}
+
+	self.selectedNodeView = nil;
+	self.nodeViewBindings = nil;
+
+	[bindingsInfo release];
+
+	nodesAC = nil;
+	edgesAC = nil;
+	document = nil;
+	draggingNode = nil;
+	destinationConnectionNode = nil;
+
+	[super dealloc];
+}
+
+
+#pragma mark NSResponder
+
+// FIXME: replace with mouseDown: and modal tracking loop
+- (void)mouseUp:(NSEvent *)anEvent {
+
+	if(! (2 == [anEvent clickCount]))
+		return;
+
+	NSPoint point = [self convertPointFromBase:[anEvent locationInWindow]];
+	
+	if(NO == NSPointInRect(point, interiorRect))
+		return;
+	
+	NSPoint location = [self _closestLocationToPoint:point];
+	
+#if 0
+	[document newNodeForRow:location.x column:location.y];
+	
+#else
+	NSLog(@"Got request to add new node for location: %@", NSStringFromPoint(location));
+
+	if(nil == [document nodeAtRow:(NSUInteger)location.x column: (NSUInteger)location.y]) {
+		addLocation = location;
+		[nodesAC add:self];
+	}
+	else {
+		NSLog(@"... that location was already occupied.");
+	}
+#endif
+}
+
+
+#pragma mark NSView
+- (BOOL)isFlipped {
+	return YES;
+}
+
+- (id)initWithFrame:(NSRect)frame {
+
+    self = [super initWithFrame:frame];
+    
+	if (self) {
+		
+		self.columnWidth = SYMBOL_WIDTH + SYMBOL_SPACING_HORIZONTAL;
+		self.rowHeight = SYMBOL_HEIGHT + SYMBOL_SPACING_VERTICAL;
+
+		[self updatePageLayoutWithPrintInfo:[NSPrintInfo sharedPrintInfo]];
+	
+		self.nodeViewBindings = [NSMutableDictionary dictionary];
+
+		bindingsInfo = [NSMutableDictionary new];
+		addLocation = NSMakePoint(-1.0f, -1.0f);
+
+		[self registerForDraggedTypes:[NSArray arrayWithObject:BLNodePBoardType]];
+		
+		drawGrid = [[NSUserDefaults standardUserDefaults] boolForKey:@"drawsGrid"];
+		drawMargins = [[NSUserDefaults standardUserDefaults] boolForKey:@"drawsMargins"];
+		gridStyle = [[NSUserDefaults standardUserDefaults] integerForKey:@"gridStyle"];
+	}
+	
+    return self;
+}
+
+
+- (void)drawRect:(NSRect)rect {
+
+	BOOL isToScreen = [[NSGraphicsContext currentContext] isDrawingToScreen];
+	
+	if(YES == isToScreen) {
+		[self _drawBackgroundInRect:rect];
+		
+		[self _drawSelectionHighlight];
+		
+		if(YES == drawGrid) {
+			if(BLGridStyleBoxes == gridStyle) {
+				[self _drawBoxStyleGrid];
+			}
+			else {
+				[self _drawLineStyleGrid];
+			}
+		}
+		
+		if(YES == drawMargins)
+			[self _drawMargins];
+	}
+	
+	if([self.nodeViewBindings count] > 0)
+		[self _drawConnections];
+}
+
+
+#pragma mark NSNibAwaking
+- (void)awakeFromNib {
+	if(nil != nodesAC)
+		[self bind:@"nodes" toObject:nodesAC withKeyPath:@"content" options:nil];
+	if(nil != edgesAC)
+		[self bind:@"edges" toObject:edgesAC withKeyPath:@"content" options:nil];
+	
+	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+	
+	[defaults addObserver:self forKeyPath:@"drawsGrid" options:0 context:(void *)0x11];
+	[defaults addObserver:self forKeyPath:@"drawsMargins" options:0 context:(void *)0x12];
+	[defaults addObserver:self forKeyPath:@"gridStyle" options:0 context:(void *)0x13];
+}
+
+
+#pragma mark Key Value Observing
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
+	
+	NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+	
+	if(object == (void *)defaults) {
+		// user defaults changed
+		switch((int)context) {
+			case 0x11:
+				drawGrid = [defaults boolForKey:@"drawsGrid"];
+				break;
+				
+			case 0x12:
+				drawMargins = [defaults boolForKey:@"drawsMargins"];
+				break;
+				
+			case 0x13:
+				gridStyle = [defaults integerForKey:@"gridStyle"];
+				break;
+				
+			default:
+				break;
+		}
+		
+		[self setNeedsDisplay:YES];
+		return;
+	}
+
+	SEL observationSelector = NSSelectorFromString([NSString stringWithFormat:@"_observeChangeTo%@Binding:object:", context]);
+	
+	if(YES == [self respondsToSelector:observationSelector]) {
+
+#if 0
+		NSLog(@"Asked to observe value for keyPath:%@ ofObject:%@ change:%@", keyPath, object, change);
+		
+		if(YES == [object isKindOfClass:[NSArrayController class]]) {
+			NSLog(@"Arranged Object IDs:\n%@", [[object arrangedObjects] valueForKey:@"objectID"]);
+		}
+#endif
+
+		[self performSelector:observationSelector withObject:change withObject:object];
+	}
+	else
+		[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
+	
+}
+
+
+#pragma mark Key Value Binding
+- (void)bind:(NSString *)bindingName
+	toObject:(id)observableObject
+ withKeyPath:(NSString *)observableKeyPath
+	 options:(NSDictionary *)options {
+	
+	if(NO == [[bindingsSymbols allKeys] containsObject:bindingName])
+		return [super bind:bindingName toObject:observableObject withKeyPath:observableKeyPath options:options];
+		
+	if(nil == observableObject || nil == observableKeyPath) {
+		[bindingsInfo removeObjectForKey:bindingName];
+		return;
+	}
+	
+	[bindingsInfo setObject:[NSDictionary dictionaryWithObjectsAndKeys:
+							 observableKeyPath, @"keyPath",
+							 observableObject, @"object",
+							 nil]
+					 forKey:bindingName];
+	
+	[observableObject addObserver:self
+					   forKeyPath:observableKeyPath
+						  options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew
+						  context:(void *)[bindingsSymbols objectForKey:bindingName]];
+}
+
+- (void)unbind:(NSString *)binding {
+	if(YES == [[bindingsSymbols allKeys] containsObject:binding])
+		[bindingsInfo removeObjectForKey:binding];
+	else
+		[super unbind:binding];	
+}
+
+
+#pragma mark NSDraggingDestination
+- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender {
+
+	if(NO == [[sender draggingSource] isKindOfClass:[BLNodeView class]])
+		return NSDragOperationNone;
+
+	// hide the node view
+	NSURL *objectURI = [NSKeyedUnarchiver unarchiveObjectWithData:[[sender draggingPasteboard] dataForType:BLNodePBoardType]];
+
+	draggingNode = [document nodeForURI:objectURI];
+	
+	BLNodeView *draggingView = [self.nodeViewBindings objectForKey:[draggingNode objectID]];
+	
+	[draggingView setHidden:YES];
+	
+	return NSDragOperationGeneric;
+}
+
+- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender {
+	
+	NSPoint draggingLocation = [self convertPointFromBase:[sender draggedImageLocation]];
+	NSPoint nodeIndex = [self _closestLocationToPoint:draggingLocation];
+	BOOL updateLayout = NO;
+	
+	nodeIndex.x++; nodeIndex.y++;
+	
+	if(nodeIndex.x > self.maxColumn) {
+		self.maxColumn = nodeIndex.x;
+		updateLayout = YES;
+	}
+	if(nodeIndex.y > self.maxRow) {
+		self.maxRow = nodeIndex.y;
+		updateLayout = YES;
+	}
+	if(YES == updateLayout) {
+		[self updatePageLayoutWithPrintInfo:[NSPrintInfo sharedPrintInfo]];
+		[self setNeedsDisplay:YES];
+	}
+	
+	return NSDragOperationGeneric;
+}
+
+- (BOOL)wantsPeriodicDraggingUpdates {
+	return YES;
+}
+
+- (void)draggingExited:(id <NSDraggingInfo>)sender {
+
+	BLNodeView *draggingView = [self.nodeViewBindings objectForKey:[draggingNode objectID]];
+	
+	[draggingView setHidden:NO];
+	draggingNode = nil;
+}
+
+- (void)draggingEnded:(id <NSDraggingInfo>)sender {
+	
+	BLNodeView *draggingView = [self.nodeViewBindings objectForKey:[draggingNode objectID]];
+	
+	[draggingView setHidden:NO];
+	draggingNode = nil;
+	
+	[self setNeedsDisplay:YES];
+}
+
+- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender {
+	
+	NSPoint draggingLocation = [self convertPointFromBase:[sender draggedImageLocation]];
+
+#if 1
+	NSPoint newLocation = [self _closestLocationToPoint:draggingLocation];
+	BOOL updateLayout = NO;
+	
+	if(newLocation.x > self.maxColumn) {
+		self.maxColumn = newLocation.x;
+		updateLayout = YES;
+	}
+	if(newLocation.y > self.maxRow) {
+		self.maxRow = newLocation.y;
+		updateLayout = YES;
+	}
+	if(YES == updateLayout)
+		[self updatePageLayoutWithPrintInfo:[NSPrintInfo sharedPrintInfo]];
+	
+#else
+	draggingLocation.x += (SYMBOL_HALF_WIDTH + SYMBOL_SPACING_HORIZONTAL * 0.5f);
+	draggingLocation.y -= (SYMBOL_HALF_HEIGHT + SYMBOL_SPACING_VERTICAL * 0.5f);
+	
+	NSPoint newLocation = [self _closestLocationToPoint:draggingLocation];
+#endif
+	BLNodeView *draggingView = [self.nodeViewBindings objectForKey:[draggingNode objectID]];
+		
+	[document moveNode:draggingNode toRow:newLocation.y column:newLocation.x];
+	[draggingView setHidden:NO];
+	draggingNode = nil;
+
+	return YES;
+}
+
+
+#pragma mark Private Methods
+- (void)_observeChangeToNodesBinding:(NSDictionary *)change object:(id)object {
+	
+	NSUInteger kind = [[change objectForKey:NSKeyValueChangeKindKey] unsignedIntegerValue];
+	
+	id currentNodes = nil;
+	NSArray *addedNodes = nil;
+	NSArray *removedNodes = nil;
+	
+	switch (kind) {
+			
+		case NSKeyValueChangeSetting:
+			
+			currentNodes = [object content];
+			
+			NSSet *oldNodeIDs = [NSSet setWithArray:[self.nodeViewBindings allKeys]];
+			NSSet *newNodeIDs = [NSSet setWithArray:[currentNodes valueForKey:@"objectID"]];
+			
+			NSMutableSet *unchangedNodeIDs = [oldNodeIDs mutableCopy];
+			NSMutableSet *removedNodeIDs = [oldNodeIDs mutableCopy];
+			NSMutableSet *addedNodeIDs = [newNodeIDs mutableCopy];
+			
+			[unchangedNodeIDs intersectSet:newNodeIDs];
+			[removedNodeIDs minusSet:unchangedNodeIDs];
+			[addedNodeIDs minusSet:unchangedNodeIDs];
+			
+			NSMutableSet *added = [NSMutableSet set];
+			NSMutableSet *removed = [NSMutableSet set];
+			
+			for(id node in currentNodes) {
+				
+				NSManagedObjectID *nodeID = [node objectID];
+				
+				if(YES == [removedNodeIDs containsObject:nodeID]) {
+					[removed addObject:node];
+				}
+				else if(YES == [addedNodeIDs containsObject:nodeID]) {
+					[added addObject:node];
+				}
+			}
+			
+			addedNodes = [added allObjects];
+			removedNodes = [removed allObjects];
+			
+			[unchangedNodeIDs release];
+			[removedNodeIDs release];
+			[addedNodeIDs release];
+			
+			break;
+			
+		case NSKeyValueChangeReplacement:
+			removedNodes = [self.nodeViewBindings allKeys];
+			addedNodes = [change objectForKey:NSKeyValueChangeNewKey];
+			break;
+			
+		case NSKeyValueChangeInsertion:
+			addedNodes = [[(NSArrayController *)object arrangedObjects] objectsAtIndexes:[change objectForKey:NSKeyValueChangeIndexesKey]];
+			break;
+			
+		case NSKeyValueChangeRemoval:
+			removedNodes = [[(NSArrayController *)object arrangedObjects] objectsAtIndexes:[change objectForKey:NSKeyValueChangeIndexesKey]];
+			break;
+			
+		default:
+			break;
+	}
+	
+	self.maxRow = 0;
+	self.maxColumn = 0;
+	
+	if( [removedNodes count] > 0)
+		[self _deleteViewsforRemovedNodes:removedNodes];
+	if( [addedNodes count] > 0)
+		[self _createViewsForAddedNodes:addedNodes];
+}
+
+- (void)_observeChangeToEdgesBinding:(NSDictionary *)change object:(id)object {
+	[self setNeedsDisplay:YES];
+}
+
+- (void)_updateGridPath {
+	
+	NSBezierPath *path = [NSBezierPath bezierPath];
+	
+	[path setLineWidth:1.0f];
+	[path setWindingRule:NSEvenOddWindingRule];
+	
+	// draw vertical strips for the columns
+	NSRect columnRect = interiorRect;
+	CGFloat rightLimit = NSMaxX(interiorRect);
+	CGFloat rightColumnEdge;
+	
+	columnRect.size.width = SYMBOL_WIDTH;
+	columnRect.size.height -= self.vPadding * 2.0f;
+	columnRect.origin.y += self.vPadding;
+	columnRect.origin.x += self.hPadding;
+	rightColumnEdge = NSMaxX(columnRect);
+	
+	while(rightColumnEdge <= rightLimit) {
+		[path appendBezierPathWithRect:columnRect];
+		columnRect.origin.x += self.columnWidth;
+		rightColumnEdge += self.columnWidth;
+	}
+	
+	// draw horizontal strips for the rows
+	NSRect rowRect = interiorRect;
+	CGFloat bottomLimit = NSMaxY(interiorRect) - self.vPadding;
+	CGFloat bottomRowEdge;
+	
+	rowRect.size.height = SYMBOL_HEIGHT;
+	rowRect.origin.y += self.vPadding;
+	rowRect.origin.x += self.hPadding;
+	rowRect.size.width -= self.hPadding * 2.0f;
+	bottomRowEdge = NSMaxY(rowRect);
+	
+	while(bottomRowEdge <= bottomLimit) {
+		[path appendBezierPathWithRect:rowRect];
+		rowRect.origin.y += self.rowHeight;
+		bottomRowEdge  += self.rowHeight;
+	}
+	
+	self.gridPath = path;
+}
+
+
+#pragma mark Drawing
+- (void)_drawSelectionHighlight {
+
+	BLNodeView *nodeView = [self.nodeViewBindings objectForKey:[[[nodesAC selectedObjects] lastObject] objectID]];
+	NSBezierPath *selectionPath = [[nodeView path] copy];
+	
+	if(nil == selectionPath)
+		return;
+	
+	NSAffineTransform *translateOrigin = [[NSAffineTransform alloc] init];
+	NSAffineTransform *scale = [[NSAffineTransform alloc] init];
+	NSRect pathBounds = [selectionPath bounds];
+
+	[translateOrigin translateXBy:-(pathBounds.origin.x + pathBounds.size.width * 0.5f) yBy:-(pathBounds.origin.y + pathBounds.size.height * 0.5f)];
+	[selectionPath transformUsingAffineTransform:translateOrigin];
+		
+	[scale scaleXBy:1.05f yBy:-1.05f];
+	[selectionPath transformUsingAffineTransform:scale];
+	[translateOrigin invert];
+	[selectionPath transformUsingAffineTransform:translateOrigin];
+	[translateOrigin release];
+
+	
+	NSAffineTransform *translateToView = [[NSAffineTransform alloc] init];
+	NSPoint offset = [nodeView frame].origin;
+
+	[translateToView translateXBy:offset.x yBy:offset.y];
+	[selectionPath transformUsingAffineTransform:translateToView];
+	[selectionPath fill];
+	
+	[NSGraphicsContext saveGraphicsState];
+	NSSetFocusRingStyle(NSFocusRingOnly);
+	[selectionPath fill];
+	[NSGraphicsContext restoreGraphicsState];
+	 
+	[scale release];
+	[translateToView release];
+	[selectionPath release];
+}
+
+- (void)_drawBackgroundInRect:(NSRect)rect {
+	[[NSColor whiteColor] set];
+	[NSBezierPath fillRect:rect];
+}
+
+- (void)_drawMargins {
+	[marginColor set];
+	
+	NSBezierPath *path = [NSBezierPath bezierPathWithRect:interiorRect];
+	[path setLineJoinStyle:NSBevelLineJoinStyle];
+	[path setLineWidth:1.0f];
+	[path stroke];
+}
+
+- (void)_drawLineStyleGrid {
+	
+	[gridLineColor set];
+	[NSBezierPath setDefaultLineWidth:1.0f];
+	CGFloat leftEdge = self.leftMargin + self.hPadding;
+	CGFloat rightEdge = self.pageWidth - (self.rightMargin + self.hPadding);
+	CGFloat topEdge = self.topMargin + self.vPadding;
+	CGFloat bottomEdge = self.pageHeight - (self.bottomMargin + self.vPadding);
+	
+	// vertical
+	CGFloat xStart = leftEdge + SYMBOL_WIDTH * 0.5f;
+	NSPoint p1 = NSMakePoint(xStart, topEdge);
+	NSPoint p2 = NSMakePoint(xStart, bottomEdge);
+	
+	while(p1.x < rightEdge) {
+		[NSBezierPath strokeLineFromPoint:p1 toPoint:p2];
+		p2.x = p1.x = p1.x + self.columnWidth;
+	}
+	
+	// horizontal
+	CGFloat yStart = topEdge + SYMBOL_HEIGHT * 0.5f;
+	
+	p1 = NSMakePoint(leftEdge, yStart);
+	p2 = NSMakePoint(rightEdge, yStart);
+	
+	while(p1.y < bottomEdge) {
+		[NSBezierPath strokeLineFromPoint:p1 toPoint:p2];
+		p2.y = p1.y = p1.y + self.rowHeight;
+	}
+}
+
+- (void)_drawBoxStyleGrid {
+	
+	[gridLineColor set];
+	[gridPath stroke];
+	
+	[gridFillColor set];
+	
+	// draw vertical strips for the columns
+	NSRect columnRect = interiorRect;
+	CGFloat rightLimit = NSMaxX(interiorRect);
+	CGFloat rightColumnEdge;
+	
+	columnRect.size.width = SYMBOL_WIDTH;
+	columnRect.size.height -= self.vPadding * 2.0f;
+	columnRect.origin.y += self.vPadding;
+	columnRect.origin.x += self.hPadding;
+	rightColumnEdge = NSMaxX(columnRect);
+	
+	while(rightColumnEdge <= rightLimit) {
+		[NSBezierPath fillRect:columnRect];
+		columnRect.origin.x += self.columnWidth;
+		rightColumnEdge += self.columnWidth;
+	}
+	
+	// draw horizontal strips for the rows
+	NSRect rowRect = interiorRect;
+	CGFloat bottomLimit = NSMaxY(interiorRect);
+	CGFloat bottomRowEdge;
+	
+	rowRect.size.height = SYMBOL_HEIGHT;
+	rowRect.size.width -= self.hPadding * 2.0f;
+	rowRect.origin.y += self.vPadding;
+	rowRect.origin.x += self.hPadding;
+	bottomRowEdge = NSMaxY(rowRect);
+	
+	while(bottomRowEdge <= bottomLimit) {
+		[NSBezierPath fillRect:rowRect];
+		rowRect.origin.y += self.rowHeight;
+		bottomRowEdge  += self.rowHeight;
+	}
+}
+
+- (void)_drawConnections {
+	
+	NSArray *edges = [(NSArrayController *)[[bindingsInfo objectForKey:@"edges"] objectForKey:@"object"] content];
+	
+	[NSGraphicsContext saveGraphicsState];
+	
+	[NSBezierPath setDefaultLineWidth:4.0f];
+	[NSBezierPath setDefaultLineCapStyle:NSRoundLineCapStyle];
+	[connectionLineColor set];
+	
+	CGFloat leftOffset = self.leftMargin +self.hPadding + SYMBOL_HALF_WIDTH;
+	CGFloat rightOffset = self.topMargin + self.vPadding + SYMBOL_HALF_HEIGHT;
+
+	for(BLEdge *edge in edges) {
+		
+		NSBezierPath *path = [edge path];
+		NSAffineTransform *scale = [[NSAffineTransform alloc] init];
+		NSAffineTransform *trans = [[NSAffineTransform alloc] init];
+		
+		 path = [path copy];
+		
+		[scale scaleXBy:columnWidth yBy:rowHeight];
+		[path transformUsingAffineTransform:scale];
+		[trans translateXBy:leftOffset yBy:rightOffset];
+		[path transformUsingAffineTransform:trans];
+
+		[scale release];
+		[trans release];
+
+		
+		NSBezierPath *newPath = [NSBezierPath bezierPath];
+		BLNodeView *originView;
+		NSPoint egressPoint;
+		NSPoint joinPoint;
+		
+		[path elementAtIndex:0 associatedPoints:&joinPoint];
+		if(edge.origin) {
+			originView = [self.nodeViewBindings objectForKey:[edge.origin objectID]];
+			egressPoint = [originView egressPoint];
+		}
+		else if(edge.altOrigin) {
+			originView = [self.nodeViewBindings objectForKey:[edge.altOrigin objectID]];
+			egressPoint = [originView altEgressPoint];
+		}
+		else
+			continue;
+			
+		[newPath moveToPoint:[self convertPoint:egressPoint fromView:originView]];
+		[newPath lineToPoint:joinPoint];
+		[newPath appendBezierPath:path];
+		
+		[newPath stroke];
+		
+		[path release];
+		
+		
+		BLNode *destination = [edge destination];
+
+		if(nil != destination) {
+			
+			BLNodeView *destinationView = [nodeViewBindings objectForKey:[destination objectID]];
+			NSPoint end = [self convertPoint:[destinationView ingressPoint] fromView:destinationView];
+			
+			NSAffineTransform *xform = [NSAffineTransform new];
+			[xform translateXBy:end.x yBy:end.y];
+			
+			[arrowHeadPath transformUsingAffineTransform:xform];
+			[arrowHeadPath fill];
+			[xform invert];
+			[arrowHeadPath transformUsingAffineTransform:xform];
+			[xform release];
+		}
+	}
+	
+	if(YES == isDraggingConnection) {
+		[NSBezierPath strokeLineFromPoint:connectStartLocation
+								  toPoint:connectEndLocation];
+	}
+	
+	[NSGraphicsContext restoreGraphicsState];
+}
+
+- (void)_deleteViewsforRemovedNodes:(NSArray *)removedNodes {
+	
+	for (id node in removedNodes) {
+		
+		BLNodeView *view = [nodeViewBindings objectForKey:[node objectID]];
+		
+		NSLog(@"Removing view: %@ for node: %@", view, [node objectID]);
+		
+		[view unbind:@"node"];
+		[view removeFromSuperview];
+		[nodeViewBindings removeObjectForKey:[node objectID]];
+	}
+	
+	self.selectedNodeView = nil;
+	[self setNeedsDisplay:YES];
+}
+
+- (void)_createViewsForAddedNodes:(NSArray *)addedNodes {
+	
+#if 1
+	for(id node in addedNodes) {
+		if([node rowUInteger] > self.maxRow)
+			self.maxRow = [node rowUInteger];
+		if([node columnUInteger] > self.maxColumn)
+			self.maxColumn = [node columnUInteger];
+		[self _newViewForNode:node];
+	}
+#else
+	
+	// move this logic into document
+	NSArray *remainingNodes = nil;
+	
+	// nil selection not permitted
+	if(nil == self.selectedNodeView) {
+		
+		NSAssert( [nodeViewBindings count] == 0, @"Assertion failure: nodes exist but there is no selected node." );
+		
+		BLNode *node = [addedNodes objectAtIndex:0];
+
+		[self _newViewForNode:node];
+		remainingNodes = [addedNodes subarrayWithRange:NSMakeRange(1, [addedNodes count]-1)];
+	}
+	else {
+		remainingNodes = addedNodes;
+	}
+	
+	for(id node in remainingNodes) {
+		
+		NSNumber *rowNum = [node valueForKey:@"row"];
+		NSNumber *columnNum = [node valueForKey:@"column"];
+		
+		if(nil == rowNum) {
+			NSUInteger row = [[self.selectedNodeView.node valueForKey:@"row"] unsignedIntValue] + 1;
+			[node setValue:[NSNumber numberWithUnsignedInt:row] forKey:@"row"];
+		}
+
+		if(nil == columnNum) {
+			[node setValue:[self.selectedNodeView.node valueForKey:@"column"] forKey:@"column"];
+		}
+		
+		[self _newViewForNode:node];
+	}
+#endif
+
+	[self setNeedsDisplay:YES];
+}
+
+- (NSRect)_frameForNode:(BLNode *)aNode {
+	
+	static NSRect firstQuadrantRect = { 0, 0, 1000.0f, 1000.0f };
+	
+	NSInteger column;
+	NSInteger row;
+	
+	if(YES == NSPointInRect(addLocation, firstQuadrantRect)) {
+		
+		column = addLocation.x;
+		row = addLocation.y;
+
+		[aNode setColumn:[NSNumber numberWithInteger:column]];
+		[aNode setRow:[NSNumber numberWithInteger:row]];
+		
+//		NSLog(@"Placing node at user specified location: %@", NSStringFromPoint(addLocation));
+
+		addLocation.x = -1;
+		addLocation.y = -1;
+	}
+	else {
+		
+		column = [[aNode valueForKey:@"column"] integerValue];
+		row = [[aNode valueForKey:@"row"] integerValue];
+		
+//		NSLog(@"Placing node at node's existing location: { %d, %d }", row, column);
+	}
+	
+	NSRect result =  NSMakeRect(self.leftMargin + column * self.columnWidth + self.hPadding - SYMBOL_STROKE_WIDTH*0.5f,
+								self.topMargin + row * self.rowHeight + self.vPadding - SYMBOL_STROKE_WIDTH*0.5f,
+								SYMBOL_WIDTH + SYMBOL_STROKE_WIDTH,
+								SYMBOL_HEIGHT + SYMBOL_STROKE_WIDTH);
+	
+//	NSLog(@"New frame for node view: %@", NSStringFromRect(result));
+	
+	return result;
+}
+
+- (BLNodeView *)_newViewForNode:(BLNode *)aNode {
+	
+//	NSLog(@"Creating new node view");
+	
+	return [self _newViewForNode:aNode withFrame:[self _frameForNode:aNode]];
+}
+
+- (BLNodeView *)_newViewForNode:(BLNode *)aNode withFrame:(NSRect)frame {
+	
+	NSViewController *vc = [[NSViewController alloc] initWithNibName:@"BLNodeView" bundle:[NSBundle mainBundle]];
+	BLNodeView *newView = (BLNodeView *)[vc view];
+	
+//	NSLog(@"Adding new view: %@ for node: %@", newView, [aNode objectID]);
+	
+	[newView setFrame:frame];
+	[self addSubview:newView];
+	
+	// ???: What happens to the other nib objects when we release the view controller?
+	[vc release];
+	
+	[newView setDelegate:document];
+	[newView bind:@"node" toObject:aNode withKeyPath:nil options:nil];
+	[self.nodeViewBindings setObject:newView forKey:[aNode objectID]];
+
+	[self selectNode:[newView node]];
+	
+	return newView;
+}
+
+- (NSPoint)_closestLocationToPoint:(NSPoint)aPoint {
+	
+	NSPoint result = NSZeroPoint;
+	
+#if 1
+	CGFloat minX = self.leftMargin + self.hPadding - SYMBOL_SPACING_HORIZONTAL * 0.5f;
+	CGFloat minY = self.topMargin + self.vPadding - SYMBOL_SPACING_VERTICAL * 0.5f;
+	
+	result.x = floorf((aPoint.x <= minX) ? 0 : (aPoint.x - minX) / self.columnWidth);
+	result.y = floorf((aPoint.y <= minY) ? 0 : (aPoint.y - minY) / self.rowHeight);
+#else
+	NSRect hitRect = NSInsetRect(interiorRect, SYMBOL_SPACING_HORIZONTAL * 0.5f, SYMBOL_SPACING_VERTICAL * 0.5f);
+	
+	result.x = floorf( (aPoint.x - hitRect.origin.x) / (SYMBOL_WIDTH + SYMBOL_SPACING_HORIZONTAL) );
+	result.y = floorf( (aPoint.y - hitRect.origin.y) / (SYMBOL_HEIGHT + SYMBOL_SPACING_VERTICAL) );
+#endif
+	
+//	NSLog(@"Converted point %@ to location %@", NSStringFromPoint(aPoint), NSStringFromPoint(result));
+					  
+	return result;
+}
+
+
+#pragma mark Synthetic Accessors
+- (void)setAllMargins:(CGFloat)margin {
+	self.topMargin = self.bottomMargin = self.rightMargin = self.leftMargin = margin;
+}
+
+
+#pragma mark New Operations
+- (void)selectNode:(BLNode *)aNode {
+	
+	BLNodeView *aView = [nodeViewBindings objectForKey:[aNode objectID]];
+	if(! (self == [aView superview]))
+		return;
+	
+	[nodesAC setSelectedObjects:[NSArray arrayWithObject:aNode]];
+	self.selectedNodeView = aView;
+	[[self window] makeFirstResponder:aView];
+	
+	[self setNeedsDisplay:YES];
+}
+
+- (void)updateNodeViewForChangedNodeLocation:(BLNodeView *)aNodeView {
+	[aNodeView setFrame:[self _frameForNode:[aNodeView node]]];
+}
+
+- (void)dragConnection:(NSEvent *)theEvent fromNode:(BLNode *)startNode point:(NSPoint)aPoint {
+	
+	if(nil == startNode)
+		return;
+	
+	if(NO == isDraggingConnection) {
+		
+		BLNodeView *sourceView = [self.nodeViewBindings objectForKey:[startNode objectID]];
+		
+		connectStartLocation = [self convertPoint:aPoint fromView:sourceView];
+		isDraggingConnection = YES;
+	}
+	else {
+
+		connectEndLocation = [self convertPointFromBase:[theEvent locationInWindow]];
+	
+		NSPoint location = [self _closestLocationToPoint:connectEndLocation];
+		BLNode *targetNode = [document nodeAtRow:location.y column:location.x];
+
+		if(targetNode != destinationConnectionNode) {
+			if(nil != targetNode && targetNode != startNode) {
+				if( nil == destinationConnectionNode)
+					NSLog(@"Found target for connection: %@", NSStringFromPoint(location));
+				destinationConnectionNode = targetNode;
+				[self selectNode:targetNode];
+			}
+			else {
+				if(nil != destinationConnectionNode)
+					NSLog(@"Lost target connection: %@", NSStringFromPoint(location));
+				destinationConnectionNode = nil;
+			}
+		}
+	}
+
+	[self setNeedsDisplay:YES];
+}
+
+- (void)makeConnection:(NSEvent *)theEvent fromNode:(BLNode *)startNode {
+	
+	isDraggingConnection = NO;
+	
+	if(nil != destinationConnectionNode) {
+		[document connectNode:startNode toNode:destinationConnectionNode];
+		destinationConnectionNode = nil;		
+	}
+	
+	[self setNeedsDisplay:YES];
+}
+
+- (void)makeAltConnection:(NSEvent *)theEvent fromNode:(BLNode *)startNode {
+
+	isDraggingConnection = NO;
+	
+	NSLog(@"Making alt connection from %@ to %@", NSStringFromPoint(connectStartLocation), NSStringFromPoint(connectEndLocation));
+	
+	if(nil != destinationConnectionNode) {
+		[document connectNode:startNode toAltNode:destinationConnectionNode];
+		destinationConnectionNode = nil;
+	}
+	
+	[self setNeedsDisplay:YES];
+}
+
+- (void)updatePageLayoutWithPrintInfo:(NSPrintInfo *)printInfo {
+	
+	NSString *margins = [[printInfo printer] stringForKey:@"HWMargins" inTable:@"PPD"];
+//	NSUInteger rowsPerPage = 1;
+//	NSUInteger colsPerPage = 1;
+	
+	if(nil != margins && [margins length] > 0) {
+		
+		NSScanner *scanner = [[NSScanner alloc] initWithString:margins];
+		int ml, mb, mr, mt;
+		
+		// HWMargins =  "ml mb mr mt" ... according to the Internets
+		[scanner scanInt:&ml]; self.leftMargin = ml;
+		[scanner scanInt:&mb]; self.bottomMargin = mb;
+		[scanner scanInt:&mr]; self.rightMargin = mb;
+		[scanner scanInt:&mt]; self.topMargin = mt;
+	}
+	
+	NSUInteger colsPerPage = ([printInfo paperSize].width + SYMBOL_SPACING_HORIZONTAL) / self.columnWidth;
+	NSUInteger rowsPerPage = ([printInfo paperSize].height + SYMBOL_SPACING_VERTICAL) / self.rowHeight;
+	
+	self.pageWidth = [printInfo paperSize].width;
+	self.pageHeight = [printInfo paperSize].height;
+	self.pagesWide = self.maxRow > rowsPerPage ? self.maxRow / rowsPerPage + 1 : 1;
+	self.pagesHigh = self.maxColumn > colsPerPage ? self.maxColumn / colsPerPage + 1 : 1;
+
+	interiorRect = NSMakeRect(self.topMargin,
+							  self.leftMargin,
+							  self.pageWidth * self.pagesWide - (self.leftMargin + self.rightMargin),
+							  self.pageHeight * self.pagesHigh - (self.topMargin + self.bottomMargin));
+
+	self.vPadding = ((NSUInteger)(interiorRect.size.height + SYMBOL_SPACING_VERTICAL) % (NSUInteger)self.rowHeight) * 0.5f;
+	self.hPadding = ((NSUInteger)(interiorRect.size.width + SYMBOL_SPACING_HORIZONTAL) % (NSUInteger)self.columnWidth) * 0.5f;
+	
+	[self setFrame:NSMakeRect(0, 0, self.pageWidth * self.pagesWide, self.pageHeight * self.pagesHigh)];
+			
+	[self _updateGridPath];
+}
+
+
+#pragma mark Actions
+- (IBAction)delete:(id)sender {
+	[nodesAC remove:self];
+}
+
+@end
+//
+//  BLDocument.h
+//  BranchLine
+//
+//  Created by Brent Gulanowski on 14/02/08.
+//  Copyright 2008 Bored Astronaut. All rights reserved.
+//
+
+#import <Cocoa/Cocoa.h>
+
+
+@class BLNode, BLDiagramView;
+
+
+@interface BLDocument : NSPersistentDocument {
+
+	IBOutlet NSArrayController *nodesAC;
+	IBOutlet BLDiagramView *diagramView;
+	NSMutableArray *topLevelNodes; // BLNodes
+
+	BOOL assistedMode;
+}
+
+@property (readwrite) BOOL assistedMode;
+
++ (NSFetchRequest *)nodesFetchRequestForObjectContext:(NSManagedObjectContext *)context;
+
+- (NSArray *)nodes;
+- (NSArray *)topLevelNodes;
+- (BLNode *)nodeAtRow:(NSUInteger)row column:(NSUInteger)column;
+- (BLNode *)nodeForURI:(NSURL *)objectURI;
+
+- (BLNode *)newNodeForRow:(NSUInteger)row column:(NSUInteger)column;
+
+- (void)setSymbolName:(NSString *)name forNode:(BLNode *)aNode;
+- (void)moveNode:(BLNode *)aNode toRow:(NSUInteger)newRow column:(NSUInteger)newColumn;
+
+- (void)connectNode:(BLNode *)source toNode:(BLNode *)destination;
+- (void)connectNode:(BLNode *)source toAltNode:(BLNode *)destination;
+@end
+//
+//  BLDocument.m
+//  BranchLine
+//
+//  Created by Brent Gulanowski on 14/02/08.
+//  Copyright 2008 Bored Astronaut. All rights reserved.
+//
+
+#import "BLDocument.h"
+
+#import "BLNode.h"
+#import "BLEdge.h"
+#import "BLSymbol.h"
+#import "BLNodeView.h"
+#import "BLDiagramView.h"
+#import "NSManagedObjectContext+BLAdditions.h"
+
+
+@interface BLDocument ()
+- (BLNode *)_createNodeWithSymbol:(BLSymbol *)symbol row:(NSInteger)row column:(NSInteger)column;
+- (BLEdge *)_createEdgeWithOrigin:(BLNode *)origin destination:(BLNode *)destination;
+- (void)_createGraphStartingAtRow:(NSInteger)row column:(NSInteger)column;
+- (void)_initNewDocument;
+- (void)_moveDescendantsOfNode:(BLNode *)parentNode right:(NSUInteger)right down:(NSUInteger)down;
+@end
+
+@interface NSDocument (ExposePrivates)
+
+- (void)_pageLayout:(NSPageLayout *)pageLayout wasPresentedWithResult:(int)returnCode inContext:(void *)contextInfo;
+
+@end
+
+
+
+@implementation BLDocument
+
+@synthesize assistedMode;
+
+
+NSFetchRequest *nodesFetchRequest;
+
+
+#pragma mark NSObject
++ (void)initialize {
+	
+	if(!( [self class] == [BLDocument class]))
+		return;
+
+	NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"identifier" ascending:YES];
+	
+	nodesFetchRequest = [[NSFetchRequest alloc] init];
+	[nodesFetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
+	
+	NSPrintInfo *pi = [NSPrintInfo sharedPrintInfo];
+	[pi setTopMargin:0];
+	[pi setBottomMargin:0];
+	[pi setLeftMargin:0];
+	[pi setRightMargin:0];
+	
+	[sort release];
+}
+
+-(void)dealloc {
+	[[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:@"usesAssistedMode"];
+	[super dealloc];
+}
+
+
+#pragma mark NSDocument
+- (id)initWithType:(NSString *)typeName error:(NSError **)outError {
+
+	self = [super initWithType:typeName error:outError];
+	
+	if(nil != self) {
+		
+		[[self undoManager] disableUndoRegistration];
+		
+		[BLSymbol createDefaultSymbolsInManagedObjectContext:[self managedObjectContext]];		
+		[self _initNewDocument];
+	
+		[[self undoManager] enableUndoRegistration];
+		
+		assistedMode = [[NSUserDefaults standardUserDefaults] boolForKey:@"usesAssistedMode"];
+		
+		[[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"usesAssistedMode" options:0 context:nil];
+	}
+
+	return self;
+}
+
+- (NSString *)windowNibName {
+    return @"BLDocument";
+}
+
+
+- (NSError *)willPresentError:(NSError *)inError
+{
+    // implementation continues...
+	return [super willPresentError:inError];
+}
+
+-  (NSPrintOperation *)printOperationWithSettings:(NSDictionary *)printSettings error:(NSError **)outError {
+
+	NSPrintOperation *printOp = [NSPrintOperation printOperationWithView:diagramView];
+	
+	[printOp setJobTitle:[self displayName]];
+	
+	return printOp;
+}
+
+//- (BOOL)preparePageLayout:(NSPageLayout *)pageLayout {
+//	NSLog(@"preparePageLayout");
+//}
+
+//- (void)runModalPageLayoutWithPrintInfo:(NSPrintInfo *)printInfo delegate:(id)delegate didRunSelector:(SEL)didRunSelector contextInfo:(void *)contextInfo {	
+//	[super runModalPageLayoutWithPrintInfo:printInfo delegate:delegate didRunSelector:didRunSelector contextInfo:contextInfo];
+//}
+//- (void)_document:(NSDocument *)document pageLayoutDidReturn:(int)returnCode contextInfo:(void *)contextInfo {
+//	[super _document:document pageLayoutDidReturn:returnCode contextInfo:contextInfo];
+//}
+
+//- (void)pageLayoutDidEnd:(NSPageLayout *)pageLayout returnCode:(int)returnCode  contextInfo: (void *)contextInfo;
+- (void)_pageLayout:(NSPageLayout *)pageLayout wasPresentedWithResult:(int)returnCode inContext:(void *)contextInfo {
+	
+	[diagramView updatePageLayoutWithPrintInfo:(NSPrintInfo *)contextInfo];
+	[super _pageLayout:pageLayout wasPresentedWithResult:returnCode inContext:contextInfo];
+}
+
+
+#pragma mark NSKeyValueObserving
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
+	assistedMode = [[NSUserDefaults standardUserDefaults] boolForKey:@"usesAssistedMode"];
+}
+
+
+#pragma mark NSNibAwaking
+- (void)awakeFromNib {
+	[nodesAC prepareContent];
+}
+
+
+#pragma mark BLNodeViewDelegate
+- (BLSymbol *)symbolForName:(NSString *)name {
+	return [[self managedObjectContext] objectForEntityNamed:@"Symbol" matchingValue:name forKey:@"name"];
+}
+
+
+#pragma mark Class Accessors
++ (NSFetchRequest *)nodesFetchRequestForObjectContext:(NSManagedObjectContext *)context {
+	
+	NSFetchRequest *copy = [[nodesFetchRequest copy] autorelease];
+	NSEntityDescription *ed = [NSEntityDescription entityForName:@"Node" inManagedObjectContext:context];
+	[copy setEntity:ed];
+	
+	 return copy;
+}
+
+
+#pragma mark Accessors
+- (NSArray *)nodes {
+	
+	NSError *error = nil;
+	NSFetchRequest *fetch = [BLDocument nodesFetchRequestForObjectContext:[self managedObjectContext]];
+	NSArray *result = [[self managedObjectContext] executeFetchRequest:fetch error:&error];
+	
+	if(nil != error) {
+		NSLog(@"error fetching nodes: %@", error);
+		exit(1);
+	}
+	
+	return result;
+}
+
+- (NSArray *)topLevelNodes {
+	return nil;
+}
+
+- (BLNode *)nodeAtRow:(NSUInteger)row column:(NSUInteger)column {
+	return [[self managedObjectContext] objectForEntityNamed:@"Node"
+										   matchingPredicate:[NSPredicate predicateWithFormat:@"row = %ud AND column = %ud", row, column]];
+}
+
+- (BLNode *)nodeForURI:(NSURL *)objectURI {
+	
+	NSManagedObjectContext *context = [self managedObjectContext];
+	NSPersistentStoreCoordinator *storeCoordinator = [context persistentStoreCoordinator];
+	
+	return (BLNode *)[context objectWithID:[storeCoordinator managedObjectIDForURIRepresentation:objectURI]];
+}
+
+
+#pragma mark Private Operations
+- (void)_initNewDocument {
+	[self newNodeForRow:0 column:0];
+	[[self managedObjectContext] processPendingChanges];
+}
+
+- (BLNode *)_createNodeWithSymbol:(BLSymbol *)symbol row:(NSInteger)row column:(NSInteger)column {
+	
+	NSManagedObjectContext *context = [self managedObjectContext];
+	NSEntityDescription *nodeEntity = [NSEntityDescription entityForName:@"Node" inManagedObjectContext:context];
+	
+	BLNode *newNode = [[BLNode alloc] initWithEntity:nodeEntity insertIntoManagedObjectContext:context];
+	
+	[newNode setRowUInteger:row];
+	[newNode setColumnUInteger:column];
+	
+	[newNode release];
+
+	[newNode setSymbol:symbol];
+	
+	return newNode;
+}
+
+- (BLEdge *)_createEdgeWithOrigin:(BLNode *)origin destination:(BLNode *)destination {
+	
+	NSManagedObjectContext *context = [self managedObjectContext];
+	NSEntityDescription *edgeEntity = [NSEntityDescription entityForName:@"Edge" inManagedObjectContext:context];
+	
+	BLEdge *newEdge = [[BLEdge alloc] initWithEntity:edgeEntity insertIntoManagedObjectContext:context];
+	
+	[newEdge setOrigin:origin];
+	[newEdge setDestination:destination];
+	
+	return newEdge;
+}
+
+- (void)_createGraphStartingAtRow:(NSInteger)row column:(NSInteger)column {
+	
+	NSManagedObjectContext *context = [self managedObjectContext];
+	
+	BLSymbol *startSymbol = [context objectForEntityNamed:@"Symbol" matchingValue:@"Start" forKey:@"name"];
+	BLSymbol *processSymbol = [context objectForEntityNamed:@"Symbol" matchingValue:@"Predefined" forKey:@"name"];
+	BLSymbol *endSymbol = [context objectForEntityNamed:@"Symbol" matchingValue:@"End" forKey:@"name"];
+
+	BLNode *startNode = [self _createNodeWithSymbol:startSymbol row:row column:column];
+	BLNode *processNode = [self _createNodeWithSymbol:processSymbol row:row+1 column:column];
+	BLNode *endNode = [self _createNodeWithSymbol:endSymbol row:row+2 column:column];
+	
+	[self _createEdgeWithOrigin:startNode destination:processNode];
+	[self _createEdgeWithOrigin:processNode destination:endNode];
+}
+
+- (void)_moveDescendantsOfNode:(BLNode *)parentNode right:(NSUInteger)right down:(NSUInteger)down {
+	
+	// recurse on the left path
+	BLNode *leftDescendant = parentNode.altOutgoing.destination;
+	
+	if(nil != leftDescendant) {
+		leftDescendant.columnUInteger += right;
+		leftDescendant.rowUInteger += down;
+		[self _moveDescendantsOfNode:leftDescendant right:right down:down];
+	}
+	
+	// follow the right path
+	BLNode *rightDescendant = parentNode.outgoing.destination;
+	while(nil != rightDescendant) {
+		rightDescendant.columnUInteger += right;
+		rightDescendant.rowUInteger += down;
+		
+		leftDescendant = rightDescendant.altOutgoing.destination;
+		if(nil != leftDescendant) {
+			leftDescendant.columnUInteger += right;
+			leftDescendant.rowUInteger += down;
+			[self _moveDescendantsOfNode:leftDescendant right:right down:down];
+		}
+		
+		rightDescendant = rightDescendant.outgoing.destination;
+	}
+}
+
+
+#pragma mark New Operations
+- (BLNode *)newNodeForRow:(NSUInteger)row column:(NSUInteger)column {
+	
+	if(nil != [self nodeAtRow:row column:column])
+		return nil;
+		
+	[self _createGraphStartingAtRow:row column:column];
+	
+	return [self nodeAtRow:row column:column];
+}
+
+- (void)setSymbolName:(NSString *)name forNode:(BLNode *)aNode {
+
+	// do the right thing with the model if we are in assisted mode
+	if(YES == assistedMode) {
+		
+	}
+
+	[aNode setSymbol:[[self managedObjectContext] objectForEntityNamed:@"Symbol" matchingValue:name forKey:@"name"]];
+}
+
+- (void)moveNode:(BLNode *)aNode toRow:(NSUInteger)newRow column:(NSUInteger)newColumn {
+	
+	if(YES == assistedMode) {
+
+		NSUInteger oldRow = [[aNode row] unsignedIntegerValue];
+		NSUInteger oldColumn = [[aNode column] unsignedIntegerValue];
+
+		if(0 == newRow && nil != aNode.incoming)
+			newRow++;
+			
+		[self _moveDescendantsOfNode:aNode right:newColumn - oldColumn down:newRow - oldRow];
+	}
+	
+	[aNode setColumnUInteger:newColumn];
+	[aNode setRowUInteger:newRow];
+}
+
+- (void)connectNode:(BLNode *)source toNode:(BLNode *)destination {
+	
+	NSManagedObjectContext *context = [self managedObjectContext];
+	
+	// remove existing outgoing connections
+	BLEdge *oldOutgoing = [source outgoing];
+	BLEdge *oldIncoming = [destination incoming];
+
+	if(nil != oldOutgoing) {
+		if(oldOutgoing == oldIncoming)
+			return; // they're already connected
+		
+		[context deleteObject:oldOutgoing];
+	}
+	
+	// do the link up
+	BLEdge *newConnection = [NSEntityDescription insertNewObjectForEntityForName:@"Edge"
+														  inManagedObjectContext:context];
+	
+
+#if 1
+	if(nil != oldIncoming)
+		[oldIncoming addIncomingObject:newConnection];
+	else
+		[newConnection setDestination:destination];
+	[newConnection setOrigin:source];
+
+#else
+	if(nil != oldIncoming) {
+		[context deleteObject:oldIncoming];
+	}
+	
+	[newConnection setOrigin:source];
+	[newConnection setDestination:destination];
+#endif
+}
+
+- (void)connectNode:(BLNode *)source toAltNode:(BLNode *)destination {
+	
+	NSManagedObjectContext *context = [self managedObjectContext];
+	
+	// remove any old connections
+	BLEdge *oldOutgoing = [source altOutgoing];
+	BLEdge *oldIncoming = [destination incoming];
+	
+	if(nil != oldOutgoing) {
+		[context deleteObject:oldOutgoing];
+	}
+	
+	// do the link up
+	BLEdge *newConnection = [NSEntityDescription insertNewObjectForEntityForName:@"Edge"
+														  inManagedObjectContext:context];
+	
+#if 1
+	if(nil != oldIncoming)
+		[oldIncoming addIncomingObject:newConnection];
+	else
+		[newConnection setDestination:destination];
+	[newConnection setAltOrigin:source];
+	
+#else
+	if(nil != oldIncoming) {
+		[context deleteObject:oldIncoming];
+	}
+	
+	[newConnection setAltOrigin:source];
+	[newConnection setDestination:destination];
+#endif
+}
+@end
Add a comment to this file

BLDocument.xcdatamodel/elements

Binary file added.

Add a comment to this file

BLDocument.xcdatamodel/layout

Binary file added.

+<?xml version="1.0" encoding="UTF-8"?>
+<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.02">
+	<data>
+		<int key="IBDocument.SystemTarget">1050</int>
+		<string key="IBDocument.SystemVersion">9C31</string>
+		<string key="IBDocument.InterfaceBuilderVersion">648</string>
+		<string key="IBDocument.AppKitVersion">949.26</string>
+		<string key="IBDocument.HIToolboxVersion">352.00</string>
+		<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
+			<bool key="EncodedWithXMLCoder">YES</bool>
+		</object>
+		<object class="NSArray" key="IBDocument.PluginDependencies">
+			<bool key="EncodedWithXMLCoder">YES</bool>
+			<string>com.apple.InterfaceBuilderKit</string>
+			<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+		</object>
+		<object class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
+			<bool key="EncodedWithXMLCoder">YES</bool>
+			<object class="NSCustomObject" id="1001">
+				<string key="NSClassName">BLDocument</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">15</int>
+				<int key="NSWindowBacking">2</int>
+				<string key="NSWindowRect">{{240, 323}, {630, 637}}</string>
+				<int key="NSWTFlags">536870912</int>
+				<string key="NSWindowTitle">Window</string>
+				<string key="NSWindowClass">NSWindow</string>
+				<nil key="NSViewClass"/>
+				<object class="NSView" key="NSWindowView" id="1006">
+					<nil key="NSNextResponder"/>
+					<int key="NSvFlags">256</int>
+					<object class="NSMutableArray" key="NSSubviews">
+						<bool key="EncodedWithXMLCoder">YES</bool>
+						<object class="NSScrollView" id="899399937">
+							<reference key="NSNextResponder" ref="1006"/>
+							<int key="NSvFlags">274</int>
+							<object class="NSMutableArray" key="NSSubviews">
+								<bool key="EncodedWithXMLCoder">YES</bool>
+								<object class="NSClipView" id="98471536">
+									<reference key="NSNextResponder" ref="899399937"/>
+									<int key="NSvFlags">2304</int>
+									<object class="NSMutableArray" key="NSSubviews">
+										<bool key="EncodedWithXMLCoder">YES</bool>
+										<object class="NSCustomView" id="397939574">
+											<reference key="NSNextResponder" ref="98471536"/>
+											<int key="NSvFlags">256</int>
+											<string key="NSFrameSize">{612, 792}</string>
+											<reference key="NSSuperview" ref="98471536"/>
+											<string key="NSClassName">BLDiagramView</string>
+										</object>
+									</object>
+									<string key="NSFrame">{{1, 1}, {613, 620}}</string>
+									<reference key="NSSuperview" ref="899399937"/>
+									<reference key="NSNextKeyView" ref="397939574"/>
+									<reference key="NSDocView" ref="397939574"/>
+									<object class="NSColor" key="NSBGColor">
+										<int key="NSColorSpace">6</int>
+										<string key="NSCatalogName">System</string>
+										<string key="NSColorName">controlColor</string>
+										<object class="NSColor" key="NSColor" id="371463279">
+											<int key="NSColorSpace">3</int>
+											<bytes key="NSWhite">MC42NjY2NjY2OQA</bytes>
+										</object>
+									</object>
+									<int key="NScvFlags">4</int>
+								</object>
+								<object class="NSScroller" id="136503311">
+									<reference key="NSNextResponder" ref="899399937"/>
+									<int key="NSvFlags">256</int>
+									<string key="NSFrame">{{614, 1}, {15, 620}}</string>
+									<reference key="NSSuperview" ref="899399937"/>
+									<bool key="NSEnabled">YES</bool>
+									<reference key="NSTarget" ref="899399937"/>
+									<string key="NSAction">_doScroller:</string>
+									<double key="NSCurValue">1.000000e+00</double>
+									<double key="NSPercent">7.828283e-01</double>
+								</object>
+								<object class="NSScroller" id="226133263">
+									<reference key="NSNextResponder" ref="899399937"/>
+									<int key="NSvFlags">256</int>
+									<string key="NSFrame">{{1, 621}, {613, 15}}</string>
+									<reference key="NSSuperview" ref="899399937"/>
+									<int key="NSsFlags">1</int>
+									<reference key="NSTarget" ref="899399937"/>
+									<string key="NSAction">_doScroller:</string>
+									<double key="NSPercent">1.372549e-01</double>
+								</object>
+							</object>
+							<string key="NSFrameSize">{630, 637}</string>
+							<reference key="NSSuperview" ref="1006"/>
+							<reference key="NSNextKeyView" ref="98471536"/>
+							<int key="NSsFlags">114</int>
+							<reference key="NSVScroller" ref="136503311"/>
+							<reference key="NSHScroller" ref="226133263"/>
+							<reference key="NSContentView" ref="98471536"/>
+						</object>
+					</object>
+					<string key="NSFrameSize">{630, 637}</string>
+				</object>
+				<string key="NSScreenRect">{{0, 0}, {1680, 1028}}</string>
+				<string key="NSMaxSize">{3.40282e+38, 3.40282e+38}</string>
+			</object>
+			<object class="NSCustomView" id="28830112">
+				<nil key="NSNextResponder"/>
+				<int key="NSvFlags">256</int>
+				<object class="NSMutableArray" key="NSSubviews">
+					<bool key="EncodedWithXMLCoder">YES</bool>
+					<object class="NSScrollView" id="45222436">
+						<reference key="NSNextResponder" ref="28830112"/>
+						<int key="NSvFlags">274</int>
+						<object class="NSMutableArray" key="NSSubviews">
+							<bool key="EncodedWithXMLCoder">YES</bool>
+							<object class="NSClipView" id="45006127">
+								<reference key="NSNextResponder" ref="45222436"/>
+								<int key="NSvFlags">2304</int>
+								<object class="NSMutableArray" key="NSSubviews">
+									<bool key="EncodedWithXMLCoder">YES</bool>
+									<object class="NSOutlineView" id="899501680">
+										<reference key="NSNextResponder" ref="45006127"/>
+										<int key="NSvFlags">256</int>
+										<string key="NSFrameSize">{213, 425}</string>
+										<reference key="NSSuperview" ref="45006127"/>
+										<bool key="NSEnabled">YES</bool>
+										<object class="_NSCornerView" key="NSCornerView">
+											<nil key="NSNextResponder"/>
+											<int key="NSvFlags">256</int>
+											<string key="NSFrame">{{200, 0}, {12, 17}}</string>
+										</object>
+										<object class="NSMutableArray" key="NSTableColumns">
+											<bool key="EncodedWithXMLCoder">YES</bool>
+											<object class="NSTableColumn" id="811219392">
+												<double key="NSWidth">4.800000e+01</double>
+												<double key="NSMinWidth">1.600000e+01</double>
+												<double key="NSMaxWidth">1.000000e+03</double>
+												<object class="NSTableHeaderCell" key="NSHeaderCell">
+													<int key="NSCellFlags">75628032</int>
+													<int key="NSCellFlags2">0</int>
+													<string key="NSContents">ID</string>
+													<object class="NSFont" key="NSSupport" id="26">
+														<string key="NSName">LucidaGrande</string>
+														<double key="NSSize">1.100000e+01</double>
+														<int key="NSfFlags">3100</int>
+													</object>
+													<object class="NSColor" key="NSBackgroundColor" id="54195291">
+														<int key="NSColorSpace">3</int>
+														<bytes key="NSWhite">MC4zMzMzMzI5OQA</bytes>
+													</object>
+													<object class="NSColor" key="NSTextColor" id="392435571">
+														<int key="NSColorSpace">6</int>
+														<string key="NSCatalogName">System</string>
+														<string key="NSColorName">headerTextColor</string>
+														<object class="NSColor" key="NSColor" id="781986498">
+															<int key="NSColorSpace">3</int>
+															<bytes key="NSWhite">MAA</bytes>
+														</object>
+													</object>
+												</object>
+												<object class="NSTextFieldCell" key="NSDataCell" id="97182443">
+													<int key="NSCellFlags">337772096</int>
+													<int key="NSCellFlags2">2048</int>
+													<string key="NSContents">Text Cell</string>
+													<object class="NSFont" key="NSSupport" id="600394963">
+														<string key="NSName">LucidaGrande</string>
+														<double key="NSSize">1.100000e+01</double>
+														<int key="NSfFlags">16</int>
+													</object>
+													<reference key="NSControlView" ref="899501680"/>
+													<object class="NSColor" key="NSBackgroundColor" id="358138695">
+														<int key="NSColorSpace">6</int>
+														<string key="NSCatalogName">System</string>
+														<string key="NSColorName">controlBackgroundColor</string>
+														<reference key="NSColor" ref="371463279"/>
+													</object>
+													<object class="NSColor" key="NSTextColor" id="316129110">
+														<int key="NSColorSpace">6</int>
+														<string key="NSCatalogName">System</string>
+														<string key="NSColorName">controlTextColor</string>
+														<reference key="NSColor" ref="781986498"/>
+													</object>
+												</object>
+												<int key="NSResizingMask">3</int>
+												<bool key="NSIsResizeable">YES</bool>
+												<bool key="NSIsEditable">YES</bool>
+												<reference key="NSTableView" ref="899501680"/>
+											</object>
+											<object class="NSTableColumn" id="785861625">
+												<double key="NSWidth">1.460000e+02</double>
+												<double key="NSMinWidth">4.000000e+01</double>
+												<double key="NSMaxWidth">1.000000e+03</double>
+												<object class="NSTableHeaderCell" key="NSHeaderCell">
+													<int key="NSCellFlags">75628032</int>
+													<int key="NSCellFlags2">0</int>
+													<string key="NSContents">Note</string>
+													<reference key="NSSupport" ref="26"/>
+													<reference key="NSBackgroundColor" ref="54195291"/>
+													<reference key="NSTextColor" ref="392435571"/>
+												</object>
+												<object class="NSTextFieldCell" key="NSDataCell" id="55465572">
+													<int key="NSCellFlags">337772096</int>
+													<int key="NSCellFlags2">2048</int>
+													<string key="NSContents">Text Cell</string>
+													<reference key="NSSupport" ref="600394963"/>
+													<reference key="NSControlView" ref="899501680"/>
+													<reference key="NSBackgroundColor" ref="358138695"/>
+													<reference key="NSTextColor" ref="316129110"/>
+												</object>
+												<int key="NSResizingMask">3</int>
+												<bool key="NSIsResizeable">YES</bool>
+												<bool key="NSIsEditable">YES</bool>
+												<reference key="NSTableView" ref="899501680"/>
+											</object>
+											<object class="NSTableColumn" id="10513800">
+												<double key="NSWidth">1.000000e+01</double>
+												<double key="NSMinWidth">1.000000e+01</double>
+												<double key="NSMaxWidth">3.402823e+38</double>
+												<object class="NSTableHeaderCell" key="NSHeaderCell">
+													<int key="NSCellFlags">75628032</int>
+													<int key="NSCellFlags2">0</int>
+													<string key="NSContents"/>
+													<reference key="NSSupport" ref="26"/>
+													<object class="NSColor" key="NSBackgroundColor">
+														<int key="NSColorSpace">6</int>
+														<string key="NSCatalogName">System</string>
+														<string key="NSColorName">headerColor</string>
+														<object class="NSColor" key="NSColor" id="991515684">
+															<int key="NSColorSpace">3</int>
+															<bytes key="NSWhite">MQA</bytes>
+														</object>
+													</object>
+													<reference key="NSTextColor" ref="392435571"/>
+												</object>
+												<object class="NSImageCell" key="NSDataCell" id="674016690">
+													<int key="NSCellFlags">67239424</int>
+													<int key="NSCellFlags2">33685504</int>
+													<object class="NSImage" key="NSContents">
+														<int key="NSImageFlags">549453824</int>
+														<string key="NSSize">{14, 14}</string>
+														<object class="NSMutableArray" key="NSReps">
+															<bool key="EncodedWithXMLCoder">YES</bool>
+															<object class="NSArray">
+																<bool key="EncodedWithXMLCoder">YES</bool>
+																<integer value="0"/>
+																<object class="NSCachedImageRep">
+																	<object class="NSData" key="NSTIFFRepresentation">
+																		<bytes key="NS.bytes">TU0AKgAAAvyAACBQOCQWDQeBgMMggAEsWBIAD1duYAICBvWERmNRsABgdBkAGAuCkAFQ5sQDGd9v5/Nl
++S96RyCAqaBYCzcIAOdBICT0LAQAv4bnIYv8pkoOgcQG1oBJstt1vdQOVxuBMP2sPcEVsK10KEcDWEPB
+QKBUXg8HhAQAoIhGbAMDhUFv92gtFDpxgAFvR8ABFt4WgBfN54sdksRhFd84txgzHCIpFMrN0PCYXgAG
+W0ADETiMACZ43logkIAB+tJQgApDJtAB+NV0ABfgAtABTNV5vFUqVREJ7vZ7M6bgUJFgul9xlsvmUFDY
+ZZfHAcAAVDpsAN0AO8AAoTL0ABkJvGBNtwABhuoegBaO4OgBOqJRkl2ux2LgCAMCg8nlQqM8xjMMoQiS
+IQggAcJvmGABnloZzMGcWgACILR9AAfRznSAADACe4AGibYMAAYACCKABMlYWY6GuaZpEinoCAYIQiiM
+WwuDCNIei6JIQAAfxvkgABsmgAYAAIfBvAAEoOH8ABuGMcoABIEoAyYaqMGSCwxPcXJkFMYBeF0MgBTE
+BAbBwHRNiuLo0C4N40BUAB4GeWIAGQZUliIF0MAEdpwwMax/gAEQIHWABzG+cU6AsM4AFGYxtmiWRXFa
+HwA0qAgThQFQ8C8MQ2EGPI3CNIBoGmAB6m0XIABgER9gAaBeG+AB1AIwIaAEXYAAcfx3AATb0AAXJxH4
+fpblmWQLoIDQNg4KIri0LhXBqGQbgAcBll4AAxCOvoLgmAVqmQbIAHmeh+AAEB+LyBR2LyPRjAWbBRnE
+BhTnQdJ1EUgi2giGsZCMZR1HeeRziYDBvgQNorhSCACAaBB/w2dlKnviVCm4AB8GseYAE0ZAAGuPRmgA
+K560BUqBADFwGgSA4EA+BwBn2GhVDeEBDhWDgEgmbpuHQeJwG8dh0m4dd0mzXhvmqdQAGmcWN3vDhpH6
+gTsI2CwDAAHwevaLRqu0ZBypibqsAAex96metzAAelAAAeSBw4gWpoEfyAgADwEAAAMAAAABAA4AAAEB
+AAMAAAABAA4AAAECAAMAAAAEAAADtgEDAAMAAAABAAUAAAEGAAMAAAABAAIAAAERAAQAAAABAAAACAES
+AAMAAAABAAEAAAEVAAMAAAABAAQAAAEWAAMAAAABCSQAAAEXAAQAAAABAAAC9AEcAAMAAAABAAEAAAE9
+AAMAAAABAAIAAAFSAAMAAAABAAEAAAFTAAMAAAAEAAADvodzAAcAAA8MAAADxgAAAAAACAAIAAgACAAB
+AAEAAQABAAAPDGFwcGwCAAAAbW50clJHQiBYWVogB9gAAgAUAA4ABgAxYWNzcEFQUEwAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1hcHBsxfuWj2jm7nbrKCt8q1cD1QAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAOclhZWgAAASwAAAAUZ1hZWgAAAUAAAAAUYlhZWgAAAVQAAAAUd3RwdAAA
+AWgAAAAUY2hhZAAAAXwAAAAsclRSQwAAAagAAAAOZ1RSQwAAAbgAAAAOYlRSQwAAAcgAAAAOdmNndAAA
+AdgAAAYSbmRpbgAAB+wAAAY+ZGVzYwAADiwAAABkZHNjbQAADpAAAAAubW1vZAAADsAAAAAoY3BydAAA
+DugAAAAkWFlaIAAAAAAAAGB8AAA5DAAABbxYWVogAAAAAAAAb3IAALBfAAAciVhZWiAAAAAAAAAm6AAA
+Fq8AALDfWFlaIAAAAAAAAPNSAAEAAAABFs9zZjMyAAAAAAABDEIAAAXe///zJgAAB5IAAP2R///7ov//
+/aMAAAPcAADAbGN1cnYAAAAAAAAAAQHNAABjdXJ2AAAAAAAAAAEBzQAAY3VydgAAAAAAAAABAc0AAHZj
+Z3QAAAAAAAAAAAADAQAAAgAAAB4AaQDaAV8B/ALQA9sFIAbTCNMLPA4EEJ8THxVuF6EZnxt9HT8e4CBz
+If8jfCT7JnUn5ilVKsIsKS2QLvkwYjHTM0g0wTY3N544/TpWO6Y88D41P3ZAt0H6Qz1Eg0XERvhIIklL
+SmpLhUycTbBOxE/ZUPBSC1MpVFJVfFasV9tZClo1W1lcc12AXoVfgGBzYZJivGPrZR1mVGeIaMJp+msv
+bGJtj26qb5NwZ3FEcitzGnQRdRF2F3cfeCh5L3o0ezx8Q31HfkZ/QoA4gSqCG4MMg/yE7oXihtaHyYi5
+iaaKkIt5jGGNS444jyiQG5EQkgWS9JPflMiVrJaPl22YS5krmgya8JvXnL6dnp52n0ugHqDuobuihqNS
+pB+k7qXAppKndKhbqUWqMasdrAes7q3RrquvfLBFsROx77Lcs860wbW6tq+3pbiauYu6e7tkvD+9Bb25
+vnO/NL/8wMvBo8KAw2DEQcUixgPG48fGyKPJfMpRyyPL8sy8zYbOTs8Wz97QpdFn0ifS59Oj1F7VGtXV
+1pTXVNgX2N3Zo9pj2yHb3tyV3U3eBN6733TgMODv4bLic+My4+/kpuVa5gzmuudo6BboxOl16ifq3euc
+7FTtA+2a7iXuoe8Y74bv+fCc8WLyRfMM84/0EfSM9Qr1lfYq9tb3qfiu+f78YP//AAAAFwBQAKcBGQGs
+AmkDWQR+BdsHiQl9C7gOGxCeExsVbReKGX8bQxzZHjsfkSDjIjwjmST8JlknvCkmKo0r8y1aLsEwJTGE
+MuA0PjWRNuM4NDl4Ors79D0sPls/i0C3Qd5DBEQgRUBGVkdqSHpJhkqPS5hMn02kTqhPqVCrUbJSxVPd
+VPJWBlcYWChZM1o+W0dcT11WXl5fY2BuYXZigWOQZJ9lr2bDZ85ox2m0ap9riWx0bWJuUG8+cC1xHnIQ
+cwNz93TudeZ233faeNh513rUe818u32dfnp/WoA9gSOCC4L0g9+EzIW5hqWHj4h6iWeKT4s3jByM/43g
+jsCPnpCKkW6SVpM7lB+VAJXflr2XmJhxmUiaHprzm8WclZ1mnjWfAp/PoJ+hcaJVoz2kJaUOpfam3afD
+qKipjKpwq1SsOK0brfyu3q+/sKKxgrJks0W0I7T6tcu2mbdsuD+5E7npusG7nbx6vVm+Or8ewAPA6cHN
+wrjDnsSIxXPGWsc5yA/I48m3ypPLb8xNzSzODc7uz9DQr9GN0mfTPtQS1OTVr9Z31zvX/NjC2a3amtuH
+3HHdV9473xrf9ODP4aPic+M/5Ank0uWW5lnnHOfd6J3pXOoh6vTryeyS7VfuGu7X75TwT/EH8cTygfNB
+9AT00vWq9o/3hviV+cn7Tv1t//8AAAAOADAAYwCnAPkBYwHcAoUDXgR4BdEHjQlsCzoM7g6KEA0RbBK7
+E/0VKRZMF2sYexmCGoYbjRyWHaIerR+5IMMhyyLPI9EkyiW8JqonlSh8KWEqRSsqLBEs+y3nLtQvvjCf
+MXkyUTMiM+40tzV/NkY3CzfUOJ85bjpFOyE8AjzlPcg+qj+HQGBBMEH3QrhDe0RSRTdGHkcIR/NI30nM
+SrhLoEyGTWdORE7pT41QM1DhUZZSUFMUU9tUpFVuVjdXAFfMWJpZZlowWvZbulx7XT1d/l7BX4VgTGET
+YdlinWNeZBxk2WWWZlJnEmfUaJdpXWogat9rnGxXbQ9txm5+bzdv8nCwcXFyNnL4c7Z0cHUkddd2hncy
+d914iHk0eeJ6kntFfAZ8z32bfmp/O4ALgNmBo4JmgyGD14SJhVmGMIcNh+yIzomxipSLeIxXjTWOD47Y
+j42QJJC9kV6SB5K0k2qUJJThlaCWX5cfl+SYrJlzmjea+Zu5nHedNZ30nrSfdaA5oP6hxaKLo1GkFqTb
+paGmaac2qAao2qmyqo6raqxErR6t967Tr7CwkLF4smSzV7RPtUi2PrcxuCC5CrnwutS7trycvYC+Z79S
+wEHBOMI0wzTENsU5xjrHOMgyySbKFMr6y+DNLc6p0EjSDtQN1mPZFtxo4Ijlxuzn933//wAAbmRpbgAA
+AAAAAAY2AACX6QAAW1AAAFSbAACOVgAAJ6QAABVgAABQDQAAVDkAAlwoAAIhRwABRR4AAwEAAAIAAAAa
+ADgAUgBpAH8AkQCjALQAxADTAOIA8AD+AQwBGwEsATwBTgFgAXQBiAGeAbQBzAHmAgICHwI+Al8CggKo
+As8C+AMiA08DfAOrA9sEDQRBBHYErATkBR4FWQWUBdEGDwZNBosGygcJB0oHjAfRCBkIYwivCP4JTwmj
+CfkKUQqrCwULYQu+DBsMegzdDUYNsg4gDpMPCQ+CD/8QfRD9EX4SABKBEwMTgRQBFIAVABWCFgUWjBcY
+F6kYQhjiGYoaPRrVG2kb/ByOHSEdsx5IHtsfcCAHIKEhPSHdIoAjTiQ4JRol9CbHJ5UoXikmKe4qtyuF
+LFUtJC32Ls0vqTCLMXQyYjNTNEY1OzYuNyQ4GzkYOho7IjwtPTs+ST9UQF5BZ0JuQ31ElUWxRtZIAEkx
+SmVLmEzKTfpPKVBlUa9TAFRaVbxXJliQWfpbYFzGXhVfXmClYexjNmSHZd9nS2jIalxr1G0sbn5v0HEc
+cm5zwHUWdnR31nlFett8uX6LgEWB7oOGhRaGoogwicCLVIzljoOQK5Hfk5+VbJc+mRWa8JzPnsGgt6K7
+pMOmz6jSqtOsyq7EsNSy7bUXt0O5drugvcG/2cH4xCfGaMi8yyPNjM/60l/Uutb22V/cOt/A47bmauhQ
+6mLtyfFy9LX3XPlz+xH8Yf0t/fj+iv8G/4P//wAAACIAQgBdAHYAjQCjALcAygDdAO4BAAERASIBNAFF
+AVcBaQF8AY8BpAG5AdAB6QIDAh8CPQJeAoICqQLXAwYDOANrA54D0gQHBDwEdASsBOUFHgVZBZQF0gYQ
+Bk8GkAbSBxYHXAejB+sINgiDCNEJIQl1CcoKIgp+CtsLPAueDAIMagzUDUANsg4jDpoPEw+QEBAQlBEa
+EaISLhK7E00T4BR1FQQVkBYfFrEXRRfbGHUZExmyGlUa+huhHEkc9R2fHk0e+h+nIFUhBSGzImMjIiPy
+JMYlniZ3J08oKSkGKeMqwCufLH4tXi49Lx4v/zDfMb8yojOINHk1fjaMN5c4ojmpOrM7vDzDPc0+2j/r
+QP1CDkMqREVFaEaQR7xI70oQSz1MZ02YTs1QClFJUpFT3VUwVoZX5FlIWq5cGl2NXv1gaWG1YwVkVGWn
+ZvxoVmm0axVsd23db0RwsXIfc5F1A3Z6d/B5a3rzfI9+Mn/OgWyDCYSjhjmHzIleiu2Meo4Hj5mRIZK0
+lEKVzpdomR2a3ZyfnlGgBaG3o2qlGqbNqIaqRawQreSvwrGys621v7fGuXK7Jrzbvp3AY8I3xBnF/sf0
+yfnMC84k0E7Se9Sw1u3ZLNs63UXfduGy5AXmXOjF6yTtfu/S8gT0F/YK99H5b/rf/Bb9Bv3d/p3/Tv//
+AAAAOQBtAJQAtADQAOoBAAEWASwBQwFcAXcBkwGxAdIB9QIcAkYCcwKjAtgDEANKA4kDzQQVBF4EqQT1
+BUMFkwXlBjoGkgbtB0sHsAgZCIcI+QlvCekKZQriC2AL3wxgDOcNeA4ODqwPUQ/9EK0RYRIVEsoTeRQm
+FNEVfhYtFuIXnhhlGTkaHBrnG6ccZx0nHegeqx9vIDkhByHcIrgj5yUVJjQnSChRKVgqXitqLHgtgi6R
+L6cwxjHtMxg0RTVzNp43zTkAOj47gzzOPh0/aECyQfpDSESmRgpHd0joSlpLx00wTpRQAVGCUw9UqlZT
+WAJZslteXQNegl/6YW5i4WRbZdxnc2kfauVsa23gb1FwvnIsc5p1CnaDeAF5jHtmfaV/wIG+g6SFfYdR
+iSeK+oy/jouQYZJBlCqWFpgAmeqbz52wn5WhfaNspVmnQqkgqvesxK6MsFSyJLP3tci3l7lhux280L55
+wBzBwsNwxSbG6Mi0yorMZM460BTR59O21XnXL9jg2o/cO93p35/hW+Mk5Pnm4ujf6kPrfOyh7bnuwu+/
+8Kjxi/JV8xjzzvR29R/1r/Y59sT3QPev+B/4jvjz+Ur5ovn5+lD6pPrk+yT7Zful++b8Jvxm/JH8vfzo
+/RP9P/1q/ZX9wf3s/hj+SP5//rb+7P8j/1r/kf/I//8AAGRlc2MAAAAAAAAACkNvbG9yIExDRAAAAAAA
+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+AAAAAAAAAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAABIAAAAcAEMAbwBsAG8AcgAgAEwAQwBE
+AABtbW9kAAAAAAAABhAAAJxoAAAAAMIRDgAAAAAAAAAAAAAAAAAAAAAAdGV4dAAAAABDb3B5cmlnaHQg
+QXBwbGUsIEluYy4sIDIwMDgAA</bytes>
+																	</object>
+																	<string key="NSCacheWindowColorSpace">NSCalibratedRGBColorSpace</string>
+																	<int key="NSCacheWindowBPS">8</int>
+																	<int key="NSCacheWindowBPP">24</int>
+																	<int key="NSCacheWindowIsPlanar">0</int>
+																</object>
+															</object>
+														</object>
+														<object class="NSColor" key="NSColor">
+															<int key="NSColorSpace">3</int>
+															<bytes key="NSWhite">MCAwAA</bytes>
+														</object>
+													</object>
+													<reference key="NSSupport" ref="26"/>
+													<int key="NSAlign">0</int>
+													<int key="NSScale">0</int>
+													<int key="NSStyle">0</int>
+													<bool key="NSAnimates">NO</bool>
+												</object>
+												<int key="NSResizingMask">3</int>
+												<bool key="NSIsResizeable">YES</bool>
+												<bool key="NSIsEditable">YES</bool>
+												<reference key="NSTableView" ref="899501680"/>
+											</object>
+										</object>
+										<double key="NSIntercellSpacingWidth">3.000000e+00</double>
+										<double key="NSIntercellSpacingHeight">2.000000e+00</double>
+										<reference key="NSBackgroundColor" ref="991515684"/>
+										<object class="NSColor" key="NSGridColor">
+											<int key="NSColorSpace">6</int>
+											<string key="NSCatalogName">System</string>
+											<string key="NSColorName">gridColor</string>
+											<object class="NSColor" key="NSColor">
+												<int key="NSColorSpace">3</int>
+												<bytes key="NSWhite">MC41AA</bytes>
+											</object>
+										</object>
+										<double key="NSRowHeight">1.400000e+01</double>
+										<int key="NSTvFlags">-1830813696</int>
+										<int key="NSColumnAutoresizingStyle">4</int>
+										<int key="NSDraggingSourceMaskForLocal">15</int>
+										<int key="NSDraggingSourceMaskForNonLocal">0</int>
+										<bool key="NSAllowsTypeSelect">YES</bool>
+									</object>
+								</object>
+								<string key="NSFrameSize">{200, 425}</string>
+								<reference key="NSSuperview" ref="45222436"/>
+								<reference key="NSNextKeyView" ref="899501680"/>
+								<reference key="NSDocView" ref="899501680"/>
+								<reference key="NSBGColor" ref="358138695"/>
+								<int key="NScvFlags">4</int>
+							</object>
+							<object class="NSScroller" id="565203756">
+								<reference key="NSNextResponder" ref="45222436"/>
+								<int key="NSvFlags">256</int>
+								<string key="NSFrame">{{200, 0}, {11, 425}}</string>
+								<reference key="NSSuperview" ref="45222436"/>
+								<int key="NSsFlags">256</int>
+								<reference key="NSTarget" ref="45222436"/>
+								<string key="NSAction">_doScroller:</string>
+								<double key="NSPercent">9.976526e-01</double>
+							</object>
+							<object class="NSScroller" id="948199563">
+								<reference key="NSNextResponder" ref="45222436"/>
+								<int key="NSvFlags">256</int>
+								<string key="NSFrame">{{0, 425}, {200, 11}}</string>
+								<reference key="NSSuperview" ref="45222436"/>
+								<bool key="NSEnabled">YES</bool>
+								<int key="NSsFlags">257</int>
+								<reference key="NSTarget" ref="45222436"/>
+								<string key="NSAction">_doScroller:</string>
+								<double key="NSPercent">9.389671e-01</double>
+							</object>
+						</object>
+						<string key="NSFrameSize">{211, 436}</string>
+						<reference key="NSSuperview" ref="28830112"/>
+						<reference key="NSNextKeyView" ref="45006127"/>
+						<int key="NSsFlags">176</int>
+						<reference key="NSVScroller" ref="565203756"/>
+						<reference key="NSHScroller" ref="948199563"/>
+						<reference key="NSContentView" ref="45006127"/>
+						<bytes key="NSScrollAmts">QSAAAEEgAABBgAAAQYAAAA</bytes>
+					</object>
+				</object>
+				<string key="NSFrameSize">{211, 436}</string>
+				<string key="NSClassName">NSView</string>
+			</object>
+			<object class="NSDrawer" id="136549628">
+				<nil key="NSNextResponder"/>
+				<string key="NSContentSize">{100, 100}</string>
+				<string key="NSMinContentSize">{0, 0}</string>
+				<string key="NSMaxContentSize">{10000, 10000}</string>
+				<int key="NSPreferredEdge">2</int>
+				<double key="NSLeadingOffset">0.000000e+00</double>
+				<double key="NSTrailingOffset">1.500000e+01</double>
+				<nil key="NSParentWindow"/>
+				<nil key="NSDelegate"/>
+			</object>
+			<object class="NSTreeController" id="970422959">
+				<object class="NSMutableArray" key="NSDeclaredKeys">
+					<bool key="EncodedWithXMLCoder">YES</bool>
+					<string>identifier</string>
+					<string>note</string>
+					<string>next</string>
+					<string>jumpTargetNode.identifier</string>
+					<string>smallImage</string>
+					<string>symbol.icon</string>
+				</object>
+				<bool key="NSEditable">YES</bool>
+				<object class="_NSManagedProxy" key="_NSManagedProxy">
+					<string key="NSEntityName">Node</string>
+				</object>
+				<bool key="_NSIsUsingManagedProxy">YES</bool>
+				<bool key="NSAvoidsEmptySelection">YES</bool>
+				<bool key="NSPreservesSelection">YES</bool>
+				<bool key="NSSelectsInsertedObjects">YES</bool>
+			</object>
+			<object class="NSArrayController" id="670580922">
+				<bool key="NSEditable">YES</bool>
+				<object class="_NSManagedProxy" key="_NSManagedProxy">
+					<string key="NSEntityName">Node</string>
+				</object>
+				<bool key="_NSIsUsingManagedProxy">YES</bool>
+				<bool key="_NSUsesLazyFetching">YES</bool>
+				<bool key="NSAvoidsEmptySelection">YES</bool>
+				<bool key="NSPreservesSelection">YES</bool>
+				<bool key="NSSelectsInsertedObjects">YES</bool>
+				<bool key="NSFilterRestrictsInsertion">YES</bool>
+				<bool key="NSClearsFilterPredicateOnInsertion">YES</bool>
+			</object>
+			<object class="NSArrayController" id="1047920351">
+				<bool key="NSEditable">YES</bool>
+				<bool key="NSAutomaticallyPreparesContent">YES</bool>
+				<object class="_NSManagedProxy" key="_NSManagedProxy">
+					<string key="NSEntityName">Edge</string>
+				</object>
+				<bool key="_NSIsUsingManagedProxy">YES</bool>
+				<bool key="NSAvoidsEmptySelection">YES</bool>
+				<bool key="NSPreservesSelection">YES</bool>
+				<bool key="NSSelectsInsertedObjects">YES</bool>
+				<bool key="NSFilterRestrictsInsertion">YES</bool>
+				<bool key="NSClearsFilterPredicateOnInsertion">YES</bool>
+			</object>
+		</object>
+		<object class="IBObjectContainer" key="IBDocument.Objects">
+			<object class="NSMutableArray" key="connectionRecords">
+				<bool key="EncodedWithXMLCoder">YES</bool>
+				<object class="IBConnectionRecord">
+					<object class="IBOutletConnection" key="connection">
+						<string key="label">contentView</string>
+						<reference key="source" ref="136549628"/>
+						<reference key="destination" ref="28830112"/>
+					</object>
+					<int key="connectionID">20</int>
+				</object>
+				<object class="IBConnectionRecord">
+					<object class="IBOutletConnection" key="connection">
+						<string key="label">parentWindow</string>
+						<reference key="source" ref="136549628"/>
+						<reference key="destination" ref="1005"/>
+					</object>
+					<int key="connectionID">23</int>
+				</object>
+				<object class="IBConnectionRecord">
+					<object class="IBBindingConnection" key="connection">
+						<string key="label">managedObjectContext: managedObjectContext</string>
+						<reference key="source" ref="970422959"/>
+						<reference key="destination" ref="1001"/>
+						<object class="NSNibBindingConnector" key="connector">
+							<reference key="NSSource" ref="970422959"/>
+							<reference key="NSDestination" ref="1001"/>
+							<string key="NSLabel">managedObjectContext: managedObjectContext</string>
+							<string key="NSBinding">managedObjectContext</string>
+							<string key="NSKeyPath">managedObjectContext</string>
+							<int key="NSNibBindingConnectorVersion">2</int>
+						</object>
+					</object>
+					<int key="connectionID">38</int>
+				</object>
+				<object class="IBConnectionRecord">
+					<object class="IBBindingConnection" key="connection">
+						<string key="label">value: arrangedObjects.identifier</string>
+						<reference key="source" ref="811219392"/>
+						<reference key="destination" ref="970422959"/>
+						<object class="NSNibBindingConnector" key="connector">