diff options
Diffstat (limited to 'tikzit-old/src/osx/GraphicsView.m')
-rw-r--r-- | tikzit-old/src/osx/GraphicsView.m | 1216 |
1 files changed, 1216 insertions, 0 deletions
diff --git a/tikzit-old/src/osx/GraphicsView.m b/tikzit-old/src/osx/GraphicsView.m new file mode 100644 index 0000000..efa7ecb --- /dev/null +++ b/tikzit-old/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 |