summaryrefslogtreecommitdiff
path: root/src/osx/GraphicsView.m
diff options
context:
space:
mode:
Diffstat (limited to 'src/osx/GraphicsView.m')
-rw-r--r--src/osx/GraphicsView.m1142
1 files changed, 1142 insertions, 0 deletions
diff --git a/src/osx/GraphicsView.m b/src/osx/GraphicsView.m
new file mode 100644
index 0000000..38ed1f0
--- /dev/null
+++ b/src/osx/GraphicsView.m
@@ -0,0 +1,1142 @@
+//
+// GraphicsView.m
+// TikZiT
+//
+// Copyright 2010 Aleks Kissinger. All rights reserved.
+//
+//
+// This file is part of TikZiT.
+//
+// TikZiT is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// TikZiT is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with TikZiT. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#import "GraphicsView.h"
+#import "util.h"
+#import "CALayer+DrawLabel.h"
+
+#import "NodeSelectionLayer.h"
+#import "NodeLayer.h"
+#import "EdgeControlLayer.h"
+#import "AppDelegate.h"
+#import "TikzGraphAssembler.h"
+#import "TikzSourceController.h"
+
+@interface GraphicsView (Private)
+- (void)setupLayers;
+- (void)addNodeLayers:(Node*)n;
+- (void)addEdgeLayers:(Edge*)e;
+- (void)removeNodeLayers:(Node*)n;
+- (void)resetMainOrigin;
+- (void)setMainOrigin:(NSPoint)o;
+@end
+
+static CGColorRef cgGrayColor, cgWhiteColor, cgClearColor = nil;
+
+
+@implementation GraphicsView
+
+@synthesize enabled, transformer, pickSupport, tikzSourceController;
+
+- (void)postGraphChange {
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"GraphChanged"
+ object:self];
+ [self postSelectionChange];
+}
+
+- (void)postSelectionChange {
+ [[NSNotificationCenter defaultCenter] postNotificationName:@"SelectionChanged"
+ object:self];
+}
+
+- (id)initWithFrame:(NSRect)frame {
+ self = [super initWithFrame:frame];
+ if (self) {
+ if (cgClearColor == nil) {
+ cgClearColor = CGColorGetConstantColor(kCGColorClear);
+ cgGrayColor = CGColorCreateGenericGray(0.5f, 0.5f);
+ cgWhiteColor = CGColorCreateGenericRGB(1, 1, 1, 1);
+ }
+
+ transformer = [[Transformer alloc] init];
+ mouseMode = SelectMode;
+ grid = [Grid gridWithSpacing:1.0f
+ subdivisions:4
+ transformer:transformer];
+ [grid setSize:NSSizeFromCGSize([gridLayer bounds].size)];
+ [transformer setScale:PIXELS_PER_UNIT];
+
+ [self setupLayers];
+
+ leaderNode = nil;
+ pickSupport = [[PickSupport alloc] init];
+ frameMoveMode = NO;
+
+ enabled = YES;
+ [self setGraph:[Graph graph]];
+ }
+ return self;
+}
+
+- (void)awakeFromNib {
+ AppDelegate *del = [application delegate];
+ stylePaletteController = [del stylePaletteController];
+ toolPaletteController = [del toolPaletteController];
+ [self refreshLayers];
+ [self postGraphChange];
+}
+
+- (void)setupLayers {
+ mainLayer = [CALayer layer];
+ [mainLayer setBackgroundColor:cgWhiteColor];
+ [mainLayer setFrame:CGRectIntegral(NSRectToCGRect([self bounds]))];
+ [mainLayer setOpacity:1.0f];
+ [self setLayer:mainLayer];
+ [self resetMainOrigin];
+
+ gridLayer = [CALayer layer];
+ [gridLayer setDelegate:grid];
+ [gridLayer setOpacity:0.3f];
+ [mainLayer addSublayer:gridLayer];
+
+ graphLayer = [CALayer layer];
+ [graphLayer setDelegate:self];
+ [mainLayer addSublayer:graphLayer];
+
+ hudLayer = [CALayer layer];
+ [mainLayer addSublayer:hudLayer];
+
+ selectionLayer = [SelectBoxLayer layer];
+ [mainLayer addSublayer:selectionLayer];
+
+ [transformer setOrigin:NSMakePoint(NSMidX([self bounds]),NSMidY([self bounds]))];
+ oldBounds = [self bounds];
+ [self refreshLayers];
+}
+
+// Lion resume feature
+//- (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
+// NSLog(@"got encode request");
+//}
+//- (void)restoreStateWithCoder:(NSCoder*)coder {
+// NSLog(@"got decode request");
+//}
+
+- (void)registerUndo:(NSString*)oldTikz withActionName:(NSString*)nm {
+ [documentUndoManager registerUndoWithTarget:self
+ selector:@selector(undoGraphChange:)
+ object:oldTikz];
+ [documentUndoManager setActionName:nm];
+}
+
+- (void)revertToTikz:(NSString*)tikz {
+ [tikzSourceController setTikz:tikz];
+ [tikzSourceController tryParseTikz];
+ [self refreshLayers];
+ [self postGraphChange];
+}
+
+
+- (void)undoGraphChange:(NSString*)oldTikz {
+ NSString *currentTikz = [graph tikz];
+ [self revertToTikz:oldTikz];
+ [documentUndoManager registerUndoWithTarget:self
+ selector:@selector(undoGraphChange:)
+ object:currentTikz];
+}
+
+- (void)setGraph:(Graph*)gr {
+ graph = gr;
+
+ NSEnumerator *e;
+ CALayer *layer;
+
+ e = [edgeControlLayers objectEnumerator];
+ while (layer = [e nextObject]) [layer removeFromSuperlayer];
+ edgeControlLayers = [NSMapTable mapTableWithStrongToStrongObjects];
+
+
+ e = [nodeLayers objectEnumerator];
+ while (layer = [e nextObject]) [layer removeFromSuperlayer];
+ nodeLayers = [NSMapTable mapTableWithStrongToStrongObjects];
+
+ for (Node *n in [graph nodes]) {
+ [n attachStyleFromTable:[stylePaletteController nodeStyles]];
+ [self addNodeLayers:n];
+ }
+
+ for (Edge *e in [graph edges]) {
+ [e setAttributesFromData];
+ [e attachStyleFromTable:[stylePaletteController edgeStyles]];
+ [self addEdgeLayers:e];
+ }
+}
+
+- (Graph*)graph { return graph; }
+
+- (void)setMainOrigin:(NSPoint)o {
+ o.x = round(o.x);
+ o.y = round(o.y);
+ CGRect rect = [mainLayer frame];
+ rect.origin = NSPointToCGPoint(o);
+ [mainLayer setFrame:rect];
+}
+
+- (void)resetMainOrigin {
+ NSRect bds = [self bounds];
+ bds.origin.x -= bds.size.width;
+ bds.origin.y -= bds.size.height;
+ bds.size.width *= 3;
+ bds.size.height *= 3;
+ [mainLayer setFrame:NSRectToCGRect([self bounds])];
+}
+
+- (void)refreshLayers {
+ [gridLayer setFrame:[mainLayer frame]];
+ [graphLayer setFrame:[mainLayer frame]];
+ [hudLayer setFrame:[mainLayer frame]];
+ [selectionLayer setFrame:[mainLayer frame]];
+
+ if (enabled) {
+ [hudLayer setBackgroundColor:cgClearColor];
+ } else {
+ [hudLayer setBackgroundColor:cgGrayColor];
+ }
+
+ [grid setSize:NSSizeFromCGSize([gridLayer bounds].size)];
+ [gridLayer setNeedsDisplay];
+ [graphLayer setNeedsDisplay];
+ [hudLayer setNeedsDisplay];
+
+ NSEnumerator *e = [edgeControlLayers objectEnumerator];
+ CALayer *layer;
+ while (layer = [e nextObject]) {
+ [layer setFrame:[graphLayer frame]];
+ [layer setNeedsDisplay];
+ }
+}
+
+
+- (void)viewDidEndLiveResize {
+ [super viewDidEndLiveResize];
+ NSPoint o = [transformer origin];
+ o.x += round(([self bounds].size.width - oldBounds.size.width)/2.0f);
+ o.y += round(([self bounds].size.height - oldBounds.size.height)/2.0f);
+ [transformer setOrigin:o];
+ oldBounds = [self bounds];
+ [self refreshLayers];
+}
+
+- (void)applyStyleToSelectedNodes:(NodeStyle*)style {
+ NSString *oldTikz = [graph tikz];
+
+ for (Node *n in [pickSupport selectedNodes]) {
+ [n setStyle:style];
+ [[nodeLayers objectForKey:n] setNeedsDisplay];
+ }
+
+ [self registerUndo:oldTikz withActionName:@"Apply Style to Nodes"];
+ [self refreshLayers];
+ [self postGraphChange];
+}
+
+- (void)applyStyleToSelectedEdges:(EdgeStyle*)style {
+ NSString *oldTikz = [graph tikz];
+
+ for (Edge *e in [pickSupport selectedEdges]) {
+ [e setStyle:style];
+ }
+
+ [self registerUndo:oldTikz withActionName:@"Apply Style to Edges"];
+ [self refreshLayers];
+ [self postGraphChange];
+}
+
+- (void)addNodeLayers:(Node*)n {
+ // add a node to the graph
+ [graph addNode:n];
+
+ NSPoint pt = [transformer toScreen:[n point]];
+
+ // add a node layer
+ NodeLayer *nl = [[NodeLayer alloc] initWithNode:n transformer:transformer];
+ [nl setCenter:pt];
+ [nodeLayers setObject:nl forKey:n];
+ [graphLayer addSublayer:nl];
+ [nl setNeedsDisplay];
+}
+
+- (void)removeNodeLayers:(Node*)n {
+ [[nodeLayers objectForKey:n] removeFromSuperlayer];
+ [nodeLayers removeObjectForKey:n];
+}
+
+- (void)addEdgeLayers:(Edge *)e {
+ [graph addEdge:e];
+ EdgeControlLayer *ecl = [[EdgeControlLayer alloc] initWithEdge:e andTransformer:transformer];
+ [edgeControlLayers setObject:ecl forKey:e];
+ [ecl setFrame:CGRectMake(10, 10, 100, 100)];
+ [hudLayer addSublayer:ecl];
+ [ecl setNeedsDisplay];
+}
+
+- (void)removeEdgeLayers:(Edge*)e {
+ [[edgeControlLayers objectForKey:e] removeFromSuperlayer];
+ [edgeControlLayers removeObjectForKey:e];
+ [self refreshLayers];
+}
+
+- (BOOL)circleWithCenter:(NSPoint)center andRadius:(float)radius containsPoint:(NSPoint)p {
+ float dx = center.x - p.x;
+ float dy = center.y - p.y;
+ return (dx*dx + dy*dy) <= radius*radius;
+}
+
+- (BOOL)node:(Node*)node containsPoint:(NSPoint)p {
+ NodeLayer *nl = [nodeLayers objectForKey:node];
+ return [nl nodeContainsPoint:p];
+}
+
+- (BOOL)edge:(Edge*)edge containsPoint:(NSPoint)p {
+// NSPoint center = [transformer toScreen:edge.mid];
+// float dx = center.x - p.x;
+// float dy = center.y - p.y;
+// float radius = 5.0f; // tolerence for clicks
+// return (dx*dx + dy*dy) <= radius*radius;
+
+ CGContextRef ctx = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort];
+
+ // Save the graphics state before doing the hit detection.
+ CGContextSaveGState(ctx);
+
+ NSPoint src = [transformer toScreen:[[edge source] point]];
+ NSPoint targ = [transformer toScreen:[[edge target] point]];
+ NSPoint cp1 = [transformer toScreen:[edge cp1]];
+ NSPoint cp2 = [transformer toScreen:[edge cp2]];
+
+ CGContextSetLineWidth(ctx, 8.0f);
+
+ CGContextMoveToPoint(ctx, src.x, src.y);
+ CGContextAddCurveToPoint(ctx, cp1.x, cp1.y, cp2.x, cp2.y, targ.x, targ.y);
+
+ BOOL containsPoint = CGContextPathContainsPoint(ctx, NSPointToCGPoint(p), kCGPathStroke);
+
+ CGContextSetRGBStrokeColor(ctx, 0, 0, 0, 0);
+
+ CGContextStrokePath(ctx);
+ //CGContextFlush(ctx);
+ CGContextRestoreGState(ctx);
+
+ return containsPoint;
+}
+
+- (void)shiftNodes:(NSSet*)set from:(NSPoint)source to:(NSPoint)dest {
+ float dx = dest.x - source.x;
+ float dy = dest.y - source.y;
+
+ for (Node *n in set) {
+ NSPoint p = [transformer toScreen:[n point]];
+ p = [grid snapScreenPoint:NSMakePoint(p.x+dx, p.y+dy)];
+ [n setPoint:[transformer fromScreen:p]];
+ }
+}
+
+
+- (void)mouseDown:(NSEvent*)theEvent {
+ if (!enabled) return;
+
+ [self updateMouseMode];
+
+ dragOrigin = [self convertPoint:[theEvent locationInWindow] fromView:nil];
+ dragTarget = dragOrigin;
+
+ graphTikzOnMouseDown = [graph tikz];
+
+ if ([theEvent modifierFlags] & NSCommandKeyMask) {
+ oldTransformerOrigin = [transformer origin];
+ oldMainOrigin = [self frame].origin;
+ frameMoveMode = YES;
+ return;
+ }
+
+ if (mouseMode == SelectMode) {
+ [selectionLayer setActive:YES];
+ [selectionLayer setSelectBox:NSRectAroundPoints(dragOrigin, dragOrigin)];
+ [selectionLayer setNeedsDisplay];
+
+ modifyEdge = nil;
+ NSPoint cp1, cp2;
+ for (Edge *e in [pickSupport selectedEdges]) {
+ cp1 = [transformer toScreen:[e cp1]];
+ cp2 = [transformer toScreen:[e cp2]];
+ if ([self circleWithCenter:cp1
+ andRadius:[EdgeControlLayer handleRadius]
+ containsPoint:dragOrigin])
+ {
+ mouseMode = SelectEdgeBendMode;
+ modifyEdge = e;
+ firstControlPoint = YES;
+ break;
+ } else if ([self circleWithCenter:cp2
+ andRadius:[EdgeControlLayer handleRadius]
+ containsPoint:dragOrigin])
+ {
+ mouseMode = SelectEdgeBendMode;
+ modifyEdge = e;
+ firstControlPoint = NO;
+ break;
+ }
+ }
+
+ if (modifyEdge == nil) { // skip all the rest if we're modifying an edge
+
+ leaderNode = nil;
+
+ // in first pass, try to find a leader node, under the mouse
+ for (Node* n in [graph nodes]) {
+ if ([self node:n containsPoint:dragOrigin]) {
+ leaderNode = n;
+ [gridLayer setOpacity:1.0f];
+ break;
+ }
+ }
+
+ // if we found one, deselect the others (if appropriate) and go to move mode
+ if (leaderNode != nil) {
+ startPoint = [leaderNode point];
+
+ // if we select a node, we should always deselect all edges:
+ for (Edge *e in [graph edges]) [[edgeControlLayers objectForKey:e] deselect];
+ [pickSupport deselectAllEdges];
+
+ BOOL shouldDeselect =
+ !([theEvent modifierFlags] & NSShiftKeyMask)
+ && ![pickSupport isNodeSelected:leaderNode];
+ for (Node *n in [graph nodes]) {
+ if (n != leaderNode && shouldDeselect) {
+ [pickSupport deselectNode:n];
+ [[[nodeLayers objectForKey:n] selection] deselect];
+ }
+ }
+
+ // ensure the leader node is actually selected
+ if (![pickSupport isNodeSelected:leaderNode]) {
+ [pickSupport selectNode:leaderNode];
+ [[[nodeLayers objectForKey:leaderNode] selection] select];
+ }
+
+
+ // put us in move mode
+ mouseMode = SelectMoveMode;
+ } else {
+ mouseMode = SelectBoxMode;
+
+ // if we didn't select a node, start hunting for an edge to select
+ BOOL shouldDeselect = !([theEvent modifierFlags] & NSShiftKeyMask);
+
+ if (shouldDeselect) {
+ [pickSupport deselectAllEdges];
+ for (Edge *e in graph.edges) [[edgeControlLayers objectForKey:e] deselect];
+ }
+
+ for (Edge* e in [graph edges]) {
+ // find the first node under the pointer, select it, show its controls
+ // and deselect all others if shift isn't down
+ if ([self edge:e containsPoint:dragOrigin]) {
+ for (Node *n in [pickSupport selectedNodes]) [[[nodeLayers objectForKey:n] selection] deselect];
+
+ [pickSupport deselectAllNodes];
+ [pickSupport selectEdge:e];
+ [[edgeControlLayers objectForKey:e] select];
+ break;
+ }
+ } // end for e in [graph edges]
+ } // end if leaderNode == nil
+ } // end if modifyEdge == nil
+
+ } else if (mouseMode == NodeMode) {
+ // do nothing...
+ } else if (mouseMode == EdgeMode) {
+ for (Node *n in [graph nodes]) {
+ if ([self node:n containsPoint:dragOrigin]) {
+ [[[nodeLayers objectForKey:n] selection] highlight];
+ }
+ }
+ mouseMode = EdgeDragMode;
+ } else if (mouseMode == CropMode) {
+ if ([graph hasBoundingBox]) {
+ float fudge = 3;
+
+ NSRect bb = [graph boundingBox];
+ NSPoint bl = [transformer toScreen:bb.origin];
+ NSPoint tr = [transformer
+ toScreen:NSMakePoint(bb.origin.x+bb.size.width,
+ bb.origin.y+bb.size.height)];
+ if (dragOrigin.x > bl.x-fudge && dragOrigin.x < tr.x+fudge &&
+ dragOrigin.y > tr.y-fudge && dragOrigin.y < tr.y+fudge)
+ {
+ bboxBottomTop = 1;
+ } else if (dragOrigin.x > bl.x-fudge && dragOrigin.x < tr.x+fudge &&
+ dragOrigin.y > bl.y-fudge && dragOrigin.y < bl.y+fudge)
+ {
+ bboxBottomTop = -1;
+ } else {
+ bboxBottomTop = 0;
+ }
+
+ if (dragOrigin.y > bl.y-fudge && dragOrigin.y < tr.y+fudge &&
+ dragOrigin.x > tr.x-fudge && dragOrigin.x < tr.x+fudge)
+ {
+ bboxLeftRight = 1;
+ } else if (dragOrigin.y > bl.y-fudge && dragOrigin.y < tr.y+fudge &&
+ dragOrigin.x > bl.x-fudge && dragOrigin.x < bl.x+fudge)
+ {
+ bboxLeftRight = -1;
+ } else {
+ bboxLeftRight = 0;
+ }
+
+ if (bboxBottomTop != 0 || bboxLeftRight != 0) {
+ mouseMode = CropDragMode;
+ }
+ }
+ } else {
+ printf("WARNING: MOUSE DOWN IN INVALID MODE.\n");
+ }
+
+ [self refreshLayers];
+}
+
+- (void)mouseDragged:(NSEvent *)theEvent {
+ if (!enabled) return;
+ dragTarget = [self convertPoint:[theEvent locationInWindow] fromView:nil];
+
+ if (frameMoveMode) {
+ NSPoint newTransOrigin, newMainOrigin;
+ NSPoint diff = NSMakePoint(dragTarget.x - dragOrigin.x, dragTarget.y - dragOrigin.y);
+ newTransOrigin.x = oldTransformerOrigin.x + diff.x;
+ newTransOrigin.y = oldTransformerOrigin.y + diff.y;
+ newMainOrigin.x = oldMainOrigin.x + diff.x;
+ newMainOrigin.y = oldMainOrigin.y + diff.y;
+
+ [CATransaction begin];
+ [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
+ [self setMainOrigin:newMainOrigin];
+ [CATransaction commit];
+
+ [transformer setOrigin:newTransOrigin];
+ return;
+ }
+
+ if (mouseMode == SelectBoxMode) {
+ [selectionLayer setSelectBox:NSRectAroundPoints(dragOrigin, dragTarget)];
+ [selectionLayer setNeedsDisplay];
+
+ for (Node* n in [graph nodes]) {
+ if (NSPointInRect([transformer toScreen:[n point]], [selectionLayer selectBox])) {
+ [[[nodeLayers objectForKey:n] selection] highlight];
+ } else if (!([theEvent modifierFlags] & NSShiftKeyMask)) {
+ [[[nodeLayers objectForKey:n] selection] unhighlight];
+ }
+ }
+ } else if (mouseMode == SelectMoveMode) {
+ if (leaderNode != nil) {
+ [self shiftNodes:[pickSupport selectedNodes]
+ from:[transformer toScreen:[leaderNode point]]
+ to:dragTarget];
+ } else {
+ printf("WARNING: LEADER NODE SHOULD NOT BE NIL.\n");
+ }
+
+ [self refreshLayers];
+ } else if (mouseMode == SelectEdgeBendMode) {
+ NSPoint src = [transformer toScreen:[[modifyEdge source] point]];
+ NSPoint targ = [transformer toScreen:[[modifyEdge target] point]];
+ float dx1 = targ.x - src.x;
+ float dy1 = targ.y - src.y;
+ float dx2, dy2;
+ if (firstControlPoint) {
+ dx2 = dragTarget.x - src.x;
+ dy2 = dragTarget.y - src.y;
+ } else {
+ dx2 = dragTarget.x - targ.x;
+ dy2 = dragTarget.y - targ.y;
+ }
+ float base_dist = sqrt(dx1*dx1 + dy1*dy1);
+ float handle_dist = sqrt(dx2*dx2 + dy2*dy2);
+ float wcourseness = 0.1f;
+
+ if (![modifyEdge isSelfLoop]) {
+ if (base_dist != 0) {
+ [modifyEdge setWeight:roundToNearest(wcourseness, handle_dist/base_dist)];
+ //round(handle_dist / (base_dist*wcourseness)) * wcourseness;
+ } else {
+ [modifyEdge setWeight:
+ roundToNearest(wcourseness, [transformer scaleFromScreen:handle_dist])];
+ }
+ }
+
+
+ float control_angle = good_atan(dx2, dy2);
+
+ int bcourseness = 15;
+
+ if ([modifyEdge bendMode] == EdgeBendModeBasic) {
+ float bnd;
+ float base_angle = good_atan(dx1, dy1);
+ if (firstControlPoint) {
+ bnd = base_angle - control_angle;
+ } else {
+ bnd = control_angle - base_angle + pi;
+ if (bnd > pi) bnd -= 2*pi;
+ }
+
+ [modifyEdge setBend:round(bnd * (180.0f / pi) *
+ (1.0f / (float)bcourseness)) *
+ bcourseness];
+ } else {
+ int bnd = round(control_angle * (180.0f / pi) *
+ (1.0f / (float)bcourseness)) *
+ bcourseness;
+ if (firstControlPoint) {
+ if ([theEvent modifierFlags] & NSAlternateKeyMask) {
+ if ([modifyEdge isSelfLoop]) {
+ [modifyEdge setInAngle:[modifyEdge inAngle] +
+ (bnd - [modifyEdge outAngle])];
+ } else {
+ [modifyEdge setInAngle:[modifyEdge inAngle] -
+ (bnd - [modifyEdge outAngle])];
+ }
+ }
+
+ [modifyEdge setOutAngle:bnd];
+ } else {
+ if (theEvent.modifierFlags & NSAlternateKeyMask) {
+ if ([modifyEdge isSelfLoop]) {
+ [modifyEdge setOutAngle:[modifyEdge outAngle] +
+ (bnd - [modifyEdge inAngle])];
+ } else {
+ [modifyEdge setOutAngle:[modifyEdge outAngle] -
+ (bnd - [modifyEdge inAngle])];
+ }
+ }
+
+ [modifyEdge setInAngle:bnd];
+ }
+ }
+
+ [self refreshLayers];
+ } else if (mouseMode == NodeMode) {
+ // do nothing...
+ } else if (mouseMode == EdgeDragMode) {
+ for (Node *n in [graph nodes]) {
+ if ([self node:n containsPoint:dragOrigin] ||
+ [self node:n containsPoint:dragTarget])
+ {
+ [[[nodeLayers objectForKey:n] selection] highlight];
+ } else {
+ [[[nodeLayers objectForKey:n] selection] unhighlight];
+ }
+ }
+
+ [self refreshLayers];
+ } else if (mouseMode == CropMode || mouseMode == CropDragMode) {
+ NSPoint p1 = [transformer fromScreen:[grid snapScreenPoint:dragOrigin]];
+ NSPoint p2 = [transformer fromScreen:[grid snapScreenPoint:dragTarget]];
+
+ NSRect bbox;
+ if (mouseMode == CropDragMode) {
+ bbox = [graph boundingBox];
+ if (bboxBottomTop == -1) {
+ float dy = p2.y - bbox.origin.y;
+ bbox.origin.y += dy;
+ bbox.size.height -= dy;
+ } else if (bboxBottomTop == 1) {
+ float dy = p2.y - (bbox.origin.y + bbox.size.height);
+ bbox.size.height += dy;
+ }
+
+ if (bboxLeftRight == -1) {
+ float dx = p2.x - bbox.origin.x;
+ bbox.origin.x += dx;
+ bbox.size.width -= dx;
+ } else if (bboxLeftRight == 1) {
+ float dx = p2.x - (bbox.origin.x + bbox.size.width);
+ bbox.size.width += dx;
+ }
+ } else {
+ bbox = NSRectAroundPoints(p1, p2);
+ }
+
+ [graph setBoundingBox:bbox];
+ [self postGraphChange];
+ [self refreshLayers];
+ } else {
+ printf("WARNING: MOUSE DRAGGED IN INVALID MODE.\n");
+ }
+}
+
+- (void)mouseUp:(NSEvent*)theEvent {
+ if (!enabled) return;
+
+ if (frameMoveMode) {
+ [CATransaction begin];
+ [CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
+ [self resetMainOrigin];
+ [self refreshLayers];
+ [CATransaction commit];
+ frameMoveMode = NO;
+ return;
+ }
+
+ dragTarget = [self convertPoint:[theEvent locationInWindow] fromView:nil];
+
+ if ((mouseMode & SelectMode) == SelectMode && [theEvent clickCount] == 2) {
+ for (Edge *e in [graph edges]) {
+ if ([self edge:e containsPoint:dragTarget]) {
+ if ([e bendMode] == EdgeBendModeBasic) {
+ [e convertBendToAngles];
+ [e setBendMode:EdgeBendModeInOut];
+ } else {
+ [e convertAnglesToBend];
+ [e setBendMode:EdgeBendModeBasic];
+ }
+
+ [self registerUndo:graphTikzOnMouseDown withActionName:@"Change Edge Mode"];
+ [self postGraphChange];
+ break;
+ }
+ }
+ }
+
+ if (mouseMode == SelectBoxMode) {
+ for (Node* n in [graph nodes]) {
+ if (NSPointInRect([transformer toScreen:[n point]], [selectionLayer selectBox])) {
+ [pickSupport selectNode:n];
+ [[[nodeLayers objectForKey:n] selection] select];
+ } else if (!([theEvent modifierFlags] & NSShiftKeyMask)) {
+ [pickSupport deselectNode:n];
+ [[[nodeLayers objectForKey:n] selection] deselect];
+ }
+ }
+
+ [selectionLayer setActive:NO];
+ [selectionLayer setNeedsDisplay];
+ [self postSelectionChange];
+
+ mouseMode = SelectMode;
+ } else if (mouseMode == SelectMoveMode) {
+ [gridLayer setOpacity:0.3f];
+
+ if (dragTarget.x != dragOrigin.x || dragTarget.y != dragOrigin.y) {
+ [self registerUndo:graphTikzOnMouseDown withActionName:@"Shift Nodes"];
+ }
+
+ leaderNode = nil;
+
+ [self postGraphChange];
+ mouseMode = SelectMode;
+ } else if (mouseMode == SelectEdgeBendMode) {
+ [self registerUndo:graphTikzOnMouseDown withActionName:@"Adjust Edge"];
+ [self postGraphChange];
+ mouseMode = SelectMode;
+ modifyEdge = nil;
+ } else if (mouseMode == NodeMode) {
+ NSPoint coords = [transformer fromScreen:[grid snapScreenPoint:dragTarget]];
+ Node *n = [Node nodeWithPoint:coords];
+ [n setStyle:[stylePaletteController activeNodeStyle]];
+ [graph addNode:n];
+
+ [self registerUndo:graphTikzOnMouseDown withActionName:@"Add Node"];
+
+ [self addNodeLayers:n];
+ [self postGraphChange];
+ } else if (mouseMode == EdgeDragMode) {
+ Node *src = nil;
+ Node *targ = nil;
+ BOOL found = NO; // don't break the loop until everything is unhighlighted
+ for (Node *n in [graph nodes]) {
+ [[[nodeLayers objectForKey:n] selection] unhighlight];
+ if (!found) {
+ if ([self node:n containsPoint:dragOrigin]) src = n;
+ if ([self node:n containsPoint:dragTarget]) targ = n;
+ if (src != nil && targ != nil) {
+ Edge *e = [Edge edgeWithSource:src andTarget:targ];
+ [e setStyle:[stylePaletteController activeEdgeStyle]];
+ [graph addEdge:e];
+ [self registerUndo:graphTikzOnMouseDown withActionName:@"Add Edge"];
+ [self addEdgeLayers:e];
+ found = YES;
+ }
+ }
+ }
+
+ [self postGraphChange];
+ mouseMode = EdgeMode;
+ } else if (mouseMode == CropMode || mouseMode == CropDragMode) {
+ if (dragOrigin.x == dragTarget.x && dragOrigin.y == dragTarget.y) {
+ [graph setBoundingBox:NSMakeRect(0, 0, 0, 0)];
+ [self registerUndo:graphTikzOnMouseDown withActionName:@"Clear Bounding Box"];
+ [self postGraphChange];
+ } else {
+ [self registerUndo:graphTikzOnMouseDown withActionName:@"Change Bounding Box"];
+ }
+
+ mouseMode = CropMode;
+ } else {
+ if (! ([theEvent modifierFlags] & NSCommandKeyMask))
+ printf("WARNING: MOUSE UP IN INVALID MODE.\n");
+ }
+
+ [self refreshLayers];
+}
+
+- (void)drawNode:(Node*)nd onLayer:(CALayer*)layer inContext:(CGContextRef)context {
+ NSPoint pt = [transformer toScreen:[nd point]];
+
+ NodeLayer *nl = [nodeLayers objectForKey:nd];
+ //[nl setStrokeWidth:2.0f];
+ [nl setCenter:pt andAnimateWhen:(mouseMode != SelectMoveMode)];
+}
+
+- (void)drawEdge:(Edge*)e onLayer:(CALayer*)layer inContext:(CGContextRef)context {
+ CGContextSaveGState(context);
+ NSPoint src = [transformer toScreen:[[e source] point]];
+ NSPoint targ = [transformer toScreen:[[e target] point]];
+ NSPoint cp1 = [transformer toScreen:[e cp1]];
+ NSPoint cp2 = [transformer toScreen:[e cp2]];
+
+ // all nodes have the same radius. this will need to be fixed
+ float sradius = 0;//(slayer.ghost) ? 0 : slayer.radius;
+ float tradius = 0;//(tlayer.ghost) ? 0 : tlayer.radius;
+
+ float sdx = cp1.x - src.x;
+ float sdy = cp1.y - src.y;
+ float sdist = sqrt(sdx*sdx + sdy*sdy);
+ float sshortx = (sdist==0) ? 0 : sdx/sdist * sradius;
+ float sshorty = (sdist==0) ? 0 : sdy/sdist * sradius;
+
+ float tdx = cp2.x - targ.x;
+ float tdy = cp2.y - targ.y;
+ float tdist = sqrt(tdx*tdx + tdy*tdy);
+ float tshortx = (tdist==0) ? 0 : tdx/sdist * tradius;
+ float tshorty = (tdist==0) ? 0 : tdy/sdist * tradius;
+
+ CGContextMoveToPoint(context, src.x+sshortx, src.y+sshorty);
+ CGContextAddCurveToPoint(context, cp1.x, cp1.y, cp2.x, cp2.y, targ.x+tshortx, targ.y+tshorty);
+
+ if ([e style] != nil) {
+ NSPoint p1,p2,p3;
+
+ // draw edge decoration
+ switch ([[e style] decorationStyle]) {
+ case ED_None:
+ break;
+ case ED_Tick:
+ p1 = [transformer toScreen:[e leftNormal]];
+ p2 = [transformer toScreen:[e rightNormal]];
+ CGContextMoveToPoint(context, p1.x, p1.y);
+ CGContextAddLineToPoint(context, p2.x, p2.y);
+ break;
+ case ED_Arrow:
+ p1 = [transformer toScreen:[e leftNormal]];
+ p2 = [transformer toScreen:[e midTan]];
+ p3 = [transformer toScreen:[e rightNormal]];
+ CGContextMoveToPoint(context, p1.x, p1.y);
+ CGContextAddLineToPoint(context, p2.x, p2.y);
+ CGContextAddLineToPoint(context, p3.x, p3.y);
+ break;
+ }
+ }
+
+
+ // OLD DRAW TICK CODE:
+ // float dx = targ.x - src.x;
+ // float dy = targ.y - src.y;
+ // float dist = sqrt(dx*dx+dy*dy);
+ // if (dist!=0) {
+ // CGContextMoveToPoint(context,
+ // round(mid.x + 4.0f * dy/dist),
+ // round(mid.y - 4.0f * dx/dist));
+ // CGContextAddLineToPoint(context,
+ // round(mid.x - 4.0f * dy/dist),
+ // round(mid.y + 4.0f * dx/dist));
+ // }
+
+
+ float lineWidth = [transformer scaleToScreen:0.04f];
+
+ CGContextSetLineWidth(context, lineWidth);
+ CGContextSetRGBStrokeColor(context, 0, 0, 0, 1);
+ CGContextStrokePath(context);
+
+ CGContextRestoreGState(context);
+
+ if ([e hasEdgeNode]) {
+ Node *en = [e edgeNode];
+ NSPoint mid = [transformer toScreen:[e mid]];
+ if (![[en label] isEqual:@""]) {
+ [layer drawLabel:[en label]
+ atPoint:mid
+ inContext:context
+ usingTrans:transformer];
+ }
+ }
+
+ EdgeControlLayer *ecl = [edgeControlLayers objectForKey:e];
+ [ecl setNeedsDisplay];
+}
+
+
+// draw the graph layer
+-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context {
+ for (Edge* e in [graph edges]) [self drawEdge:e onLayer:layer inContext:context];
+
+ for (Node* n in [graph nodes]) [self drawNode:n onLayer:layer inContext:context];
+
+ if ([graph hasBoundingBox]) {
+ CGRect bbox = NSRectToCGRect(NSIntegralRect(
+ [transformer rectToScreen:[graph boundingBox]]));
+ CGContextSetRGBStrokeColor(context, 1.0f, 0.7f, 0.5f, 1.0f);
+ CGContextSetLineWidth(context, 1.0f);
+ CGContextSetShouldAntialias(context, NO);
+ CGContextStrokeRect(context, bbox);
+ CGContextSetShouldAntialias(context, YES);
+ }
+
+ if (mouseMode == EdgeDragMode) {
+ CGContextMoveToPoint(context, dragOrigin.x, dragOrigin.y);
+ CGContextAddLineToPoint(context, dragTarget.x, dragTarget.y);
+ CGContextSetLineWidth(context, 2);
+ CGContextSetRGBStrokeColor(context, 0, 0, 1, 1);
+ CGContextStrokePath(context);
+ }
+}
+
+// if enabled, suppress the default "bonk" behaviour on key presses
+- (void)keyDown:(NSEvent *)theEvent {
+ if (!enabled) [super keyDown:theEvent];
+}
+
+- (void)delete:(id)sender {
+ BOOL didDelete = NO;
+ NSString *oldTikz = [graph tikz];
+
+ if ([[pickSupport selectedNodes] count] != 0) {
+ GraphChange *change = [graph removeNodes:[pickSupport selectedNodes]];
+ for (Node *n in [change affectedNodes]) [self removeNodeLayers:n];
+ for (Edge *e in [change affectedEdges]) [self removeEdgeLayers:e];
+
+ [self refreshLayers];
+ [self postGraphChange];
+ didDelete = YES;
+ }
+
+ if ([[pickSupport selectedEdges] count] != 0) {
+ [graph removeEdges:[pickSupport selectedEdges]];
+ for (Edge *e in [pickSupport selectedEdges]) [self removeEdgeLayers:e];
+ [self refreshLayers];
+ [self postGraphChange];
+ didDelete = YES;
+ }
+
+ [pickSupport deselectAllNodes];
+ [pickSupport deselectAllEdges];
+
+ if (didDelete) [self registerUndo:oldTikz withActionName:@"Delete Nodes or Edges"];
+}
+
+- (void)keyUp:(NSEvent *)theEvent {
+ if (!enabled) return;
+
+ id sender = self;
+ switch ([theEvent keyCode]) {
+ case 51: // delete
+ [self delete:sender]; // "self" is the sender
+ break;
+ case 1: // S
+ [toolPaletteController setSelectedTool:TikzToolSelect];
+ break;
+ case 45: // N
+ case 9: // V
+ [toolPaletteController setSelectedTool:TikzToolNode];
+ break;
+ case 14: // E
+ [toolPaletteController setSelectedTool:TikzToolEdge];
+ //[self updateMouseMode];
+ break;
+ case 40: // K
+ [toolPaletteController setSelectedTool:TikzToolCrop];
+ break;
+ }
+ [self refreshLayers];
+}
+
+
+- (void)deselectAll:(id)sender {
+ [pickSupport deselectAllNodes];
+ [pickSupport deselectAllEdges];
+
+ for (Node *n in [graph nodes]) {
+ [[[nodeLayers objectForKey:n] selection] deselect];
+ }
+
+ for (Edge *e in [graph edges]) {
+ [[edgeControlLayers objectForKey:e] deselect];
+ }
+
+ [self postSelectionChange];
+}
+
+- (void)selectAll:(id)sender {
+ [pickSupport selectAllNodes:graph.nodes];
+
+ for (Node *n in [graph nodes]) {
+ [[[nodeLayers objectForKey:n] selection] select];
+ }
+
+ [self postSelectionChange];
+}
+
+
+- (void)updateMouseMode {
+ switch (toolPaletteController.selectedTool) {
+ case TikzToolSelect:
+ mouseMode = SelectMode;
+ break;
+ case TikzToolNode:
+ mouseMode = NodeMode;
+ break;
+ case TikzToolEdge:
+ mouseMode = EdgeMode;
+ break;
+ case TikzToolCrop:
+ mouseMode = CropMode;
+ break;
+ }
+}
+
+- (void)setDocumentUndoManager:(NSUndoManager *)um {
+ documentUndoManager = um;
+}
+
+- (void)copy:(id)sender {
+ if ([pickSupport selectedNodes].count != 0) {
+ Graph *clip = [graph copyOfSubgraphWithNodes:[pickSupport selectedNodes]];
+ NSString *tikz = [clip tikz];
+ NSData *data = [tikz dataUsingEncoding:NSUTF8StringEncoding];
+ //NSLog(@"about to copy: %@", tikz);
+ NSPasteboard *cb = [NSPasteboard generalPasteboard];
+ [cb declareTypes:[NSArray arrayWithObject:@"tikzpicture"] owner:self];
+ [cb setData:data forType:@"tikzpicture"];
+ }
+}
+
+- (void)cut:(id)sender {
+ if ([[pickSupport selectedNodes] count] != 0) {
+ [self copy:sender];
+ [self delete:sender];
+
+ // otherwise, menu will say "Undo Delete Graph"
+ [documentUndoManager setActionName:@"Cut Graph"];
+ }
+}
+
+- (void)paste:(id)sender {
+ NSPasteboard *cb = [NSPasteboard generalPasteboard];
+ NSString *type = [cb availableTypeFromArray:[NSArray arrayWithObject:@"tikzpicture"]];
+ if (type) {
+ NSData *data = [cb dataForType:type];
+ NSString *tikz = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
+ //NSLog(@"pasting tikz:\n%@",tikz);
+ TikzGraphAssembler *ass = [[TikzGraphAssembler alloc] init];
+ if ([ass parseTikz:tikz]) {
+ //NSLog(@"tikz pasted:\n%@",tikz);
+ Graph *clip = [ass graph];
+
+ NSRect graphBounds = [graph bounds];
+ NSRect clipBounds = [clip bounds];
+ float dx = graphBounds.origin.x +
+ graphBounds.size.width -
+ clipBounds.origin.x + 0.5f;
+ [clip shiftNodes:[clip nodes] byPoint:NSMakePoint(dx, 0)];
+
+ if ([[clip nodes] count] != 0) {
+ NSString *oldTikz = [graph tikz];
+ [self deselectAll:self];
+
+ // select everything from the clipboard
+ for (Node *n in [clip nodes]) {
+ [n attachStyleFromTable:[stylePaletteController nodeStyles]];
+ [self addNodeLayers:n];
+ [pickSupport selectNode:n];
+ [[[nodeLayers objectForKey:n] selection] select];
+ }
+
+ for (Edge *e in [clip edges]) {
+ [e attachStyleFromTable:[stylePaletteController edgeStyles]];
+ [self addEdgeLayers:e];
+ }
+
+ [graph insertGraph:clip];
+
+ [self registerUndo:oldTikz withActionName:@"Paste Graph"];
+ [self refreshLayers];
+ [self postGraphChange];
+ }
+ } else {
+ NSLog(@"Error: couldn't parse tikz picture from clipboard.");
+ }
+
+ }
+}
+
+- (void)flipHorizonal:(id)sender {
+ NSString *oldTikz = [graph tikz];
+ [graph flipHorizontalNodes:[pickSupport selectedNodes]];
+ [self registerUndo:oldTikz withActionName:@"Flip Horizontal"];
+ [self postGraphChange];
+ [self refreshLayers];
+}
+
+- (void)flipVertical:(id)sender {
+ NSString *oldTikz = [graph tikz];
+ [graph flipVerticalNodes:[pickSupport selectedNodes]];
+ [self registerUndo:oldTikz withActionName:@"Flip Vertical"];
+ [self postGraphChange];
+ [self refreshLayers];
+}
+
+- (void)reverseEdgeDirection:(id)sender {
+ NSString *oldTikz = [graph tikz];
+
+ NSSet *es;
+ if ([[pickSupport selectedEdges] count] != 0) {
+ es = [pickSupport selectedEdges];
+ } else {
+ es = [graph incidentEdgesForNodes:[pickSupport selectedNodes]];
+ }
+
+ for (Edge *e in es) [e reverse];
+
+ [self registerUndo:oldTikz withActionName:@"Flip Edge Direction"];
+ [self postGraphChange];
+ [self refreshLayers];
+}
+
+- (BOOL)acceptsFirstResponder { return YES; }
+- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent { return YES; }
+- (BOOL)canBecomeKeyView { return YES; }
+
+
+@end