From dcf64eac4fdb246cf6fb169c1279b67966478173 Mon Sep 17 00:00:00 2001 From: randomguy3 Date: Wed, 18 Jan 2012 10:05:06 +0000 Subject: Rename src/linux to src/gtk (which is more accurate) git-svn-id: https://tikzit.svn.sourceforge.net/svnroot/tikzit/trunk@392 7c02a99a-9b00-45e3-bf44-6f3dd7fddb64 --- tikzit/src/gtk/GraphRenderer.m | 607 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 607 insertions(+) create mode 100644 tikzit/src/gtk/GraphRenderer.m (limited to 'tikzit/src/gtk/GraphRenderer.m') diff --git a/tikzit/src/gtk/GraphRenderer.m b/tikzit/src/gtk/GraphRenderer.m new file mode 100644 index 0000000..571390f --- /dev/null +++ b/tikzit/src/gtk/GraphRenderer.m @@ -0,0 +1,607 @@ +/* + * Copyright 2011 Alex Merry + * Copyright 2010 Chris Heunen + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#import "GraphRenderer.h" +#import "Edge+Render.h" +#import "Node+Render.h" + +static const float size = 5.0; + +float sideHandleTop(NSRect bbox) { + return (NSMinY(bbox) + NSMaxY(bbox) - size)/2.0f; +} + +float tbHandleLeft(NSRect bbox) { + return (NSMinX(bbox) + NSMaxX(bbox) - size)/2.0f; +} +void graph_renderer_expose_event(GtkWidget *widget, GdkEventExpose *event); + +@interface GraphRenderer (Private) +- (BOOL) selectionBoxContainsNode:(Node*)node; +- (BOOL) halfEdgeIncludesNode:(Node*)node; +- (enum NodeState) nodeState:(Node*)node; +- (void) renderBoundingBoxWithContext:(id)context; +- (void) renderSelectionBoxWithContext:(id)context; +- (void) renderImpendingEdgeWithContext:(id)context; +- (void) nodeNeedsRefreshing:(NSNotification*)notification; +- (void) edgeNeedsRefreshing:(NSNotification*)notification; +- (void) graphNeedsRefreshing:(NSNotification*)notification; +- (void) graphChanged:(NSNotification*)notification; +- (void) nodeStylePropertyChanged:(NSNotification*)notification; +- (void) edgeStylePropertyChanged:(NSNotification*)notification; +@end + +@implementation GraphRenderer + +- (id) initWithSurface:(NSObject *)s { + self = [super init]; + + if (self) { + surface = [s retain]; + doc = nil; + grid = [[Grid alloc] initWithSpacing:1.0f subdivisions:4 transformer:[s transformer]]; + halfEdgeOrigin = nil; + [surface setRenderDelegate:self]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(nodeStylePropertyChanged:) + name:@"NodeStylePropertyChanged" + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(edgeStylePropertyChanged:) + name:@"EdgeStylePropertyChanged" + object:nil]; + } + + return self; +} + +- (id) initWithSurface:(NSObject *)s document:(TikzDocument*)document { + self = [self initWithSurface:s]; + + if (self) { + [self setDocument:document]; + } + + return self; +} + +- (void) dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [doc release]; + [grid release]; + [surface release]; + + [super dealloc]; +} + +- (void) renderWithContext:(id)context onSurface:(id)surface { + [self renderWithContext:context]; +} + +- (void) renderWithContext:(id)context { + // blank surface + [context paintWithColor:WhiteRColor]; + + // draw grid + [grid renderGridInContext:context]; + + // draw edges + NSEnumerator *enumerator = [doc edgeEnumerator]; + Edge *edge; + while ((edge = [enumerator nextObject]) != nil) { + [edge renderToSurface:surface withContext:context selected:[doc isEdgeSelected:edge]]; + } + + // draw nodes + enumerator = [doc nodeEnumerator]; + Node *node; + while ((node = [enumerator nextObject]) != nil) { + [node renderToSurface:surface withContext:context state:[self nodeState:node]]; + } + + [self renderBoundingBoxWithContext:context]; + [self renderSelectionBoxWithContext:context]; + [self renderImpendingEdgeWithContext:context]; +} + +- (void) invalidateGraph { + [surface invalidate]; +} + +- (void) invalidateNodes:(NSSet*)nodes { + for (Node *node in nodes) { + [self invalidateNode:node]; + } +} + +- (void) invalidateEdges:(NSSet*)edges { + for (Edge *edge in edges) { + [self invalidateEdge:edge]; + } +} + +- (void) invalidateNode:(Node*)node { + if (node == nil) { + return; + } + NSRect nodeRect = [node renderBoundsWithLabelForSurface:surface]; + nodeRect = NSInsetRect (nodeRect, -2.0f, -2.0f); + [surface invalidateRect:nodeRect]; +} + +- (void) invalidateEdge:(Edge*)edge { + if (edge == nil) { + return; + } + BOOL selected = [doc isEdgeSelected:edge]; + NSRect edgeRect = [edge renderedBoundsWithTransformer:[surface transformer] whenSelected:selected]; + edgeRect = NSInsetRect (edgeRect, -2.0f, -2.0f); + [surface invalidateRect:edgeRect]; +} + +- (void) invalidateNodesHitBy:(NSPoint)point { + NSEnumerator *enumerator = [doc nodeEnumerator]; + Node *node = nil; + while ((node = [enumerator nextObject]) != nil) { + if ([self point:point hitsNode:node]) { + [self invalidateNode:node]; + } + } +} + +- (BOOL) point:(NSPoint)p hitsNode:(Node*)node { + return [node hitByPoint:p onSurface:surface]; +} + +- (BOOL) point:(NSPoint)p fuzzyHitsNode:(Node*)node { + NSRect bounds = [node renderBoundsForSurface:surface]; + return NSPointInRect(p, bounds); +} + +- (BOOL) point:(NSPoint)p hitsEdge:(Edge*)edge withFuzz:(float)fuzz { + return [edge hitByPoint:p onSurface:surface withFuzz:fuzz]; +} + +- (Node*) anyNodeAt:(NSPoint)p { + NSEnumerator *enumerator = [doc nodeEnumerator]; + Node *node; + while ((node = [enumerator nextObject]) != nil) { + if ([self point:p hitsNode:node]) { + return node; + } + } + return nil; +} + +- (Edge*) anyEdgeAt:(NSPoint)p withFuzz:(float)fuzz { + // FIXME: is there an efficient way to find the "nearest" edge + // if the fuzz is the reason we hit more than one? + NSEnumerator *enumerator = [doc edgeEnumerator]; + Edge *edge; + while ((edge = [enumerator nextObject]) != nil) { + if ([self point:p hitsEdge:edge withFuzz:fuzz]) { + return edge; + } + } + return nil; +} + +- (id) surface { + return surface; +} + +- (Transformer*) transformer { + return [surface transformer]; +} + +- (Grid*) grid { + return grid; +} + +- (PickSupport*) pickSupport { + return [doc pickSupport]; +} + +- (Graph*) graph { + return [doc graph]; +} + +- (TikzDocument*) document { + return doc; +} + +- (void) setDocument:(TikzDocument*)document { + if (doc == document) { + return; + } + + if (doc != nil) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:doc]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:[doc pickSupport]]; + } + + [document retain]; + [doc release]; + doc = document; + + if (doc != nil) { + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphNeedsRefreshing:) + name:@"GraphReplaced" object:doc]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphChanged:) + name:@"GraphChanged" object:doc]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphChanged:) + name:@"GraphBeingChanged" object:doc]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphChanged:) + name:@"GraphChangeCancelled" object:doc]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(nodeNeedsRefreshing:) + name:@"NodeSelected" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(nodeNeedsRefreshing:) + name:@"NodeDeselected" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphNeedsRefreshing:) + name:@"NodeSelectionReplaced" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(edgeNeedsRefreshing:) + name:@"EdgeSelected" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(edgeNeedsRefreshing:) + name:@"EdgeDeselected" object:[doc pickSupport]]; + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(graphNeedsRefreshing:) + name:@"EdgeSelectionReplaced" object:[doc pickSupport]]; + } + [surface invalidate]; +} + +- (NSRect) selectionBox { + return selectionBox; +} + +- (void) setSelectionBox:(NSRect)box { + NSRect invRect = NSUnionRect (selectionBox, box); + selectionBox = box; + [surface invalidateRect:NSInsetRect (invRect, -2, -2)]; +} + +- (void) clearSelectionBox { + NSRect oldRect = selectionBox; + + NSRect emptyRect; + selectionBox = emptyRect; + + [surface invalidateRect:NSInsetRect (oldRect, -2, -2)]; +} + +- (void) invalidateHalfEdge { + if (halfEdgeOrigin != nil) { + NSRect invRect = NSRectAroundPoints(halfEdgeEnd, halfEdgeOriginPoint); + invRect = NSUnionRect(invRect, [halfEdgeOrigin renderBoundsWithLabelForSurface:surface]); + + NSEnumerator *enumerator = [doc nodeEnumerator]; + Node *node; + while ((node = [enumerator nextObject]) != nil) { + if ([self point:halfEdgeEnd fuzzyHitsNode:node]) { + invRect = NSUnionRect(invRect, [node renderBoundsWithLabelForSurface:surface]); + } + } + [surface invalidateRect:NSInsetRect (invRect, -2.0f, -2.0f)]; + } +} + +- (void) setHalfEdgeFrom:(Node*)origin to:(NSPoint)end { + [self invalidateHalfEdge]; + + if (halfEdgeOrigin != origin) { + [self invalidateNode:halfEdgeOrigin]; + halfEdgeOrigin = origin; + halfEdgeOriginPoint = [[surface transformer] toScreen:[origin point]]; + [self invalidateNode:origin]; + } + + if (origin != nil) { + halfEdgeEnd = end; + [self invalidateHalfEdge]; + } +} + +- (void) clearHalfEdge { + [self invalidateHalfEdge]; + halfEdgeOrigin = nil; +} + +- (BOOL) boundingBoxHandlesShown { + return showBoundingBoxHandles; +} + +- (void) setBoundingBoxHandlesShown:(BOOL)shown { + if (showBoundingBoxHandles != shown) { + showBoundingBoxHandles = shown; + [self invalidateGraph]; + } +} + +- (ResizeHandle) boundingBoxResizeHandleAt:(NSPoint)p { + NSRect bbox = [[surface transformer] rectToScreen:[[self graph] boundingBox]]; + if (p.x >= NSMaxX(bbox)) { + if (p.x <= NSMaxX(bbox) + size) { + if (p.y >= NSMaxY(bbox)) { + if (p.y <= NSMaxY(bbox) + size) { + return SouthEastHandle; + } + } else if (p.y <= NSMinY(bbox)) { + if (p.y >= NSMinY(bbox) - size) { + return NorthEastHandle; + } + } else { + float eastHandleTop = sideHandleTop(bbox); + if (p.y >= eastHandleTop && p.y <= (eastHandleTop + size)) { + return EastHandle; + } + } + } + } else if (p.x <= NSMinX(bbox)) { + if (p.x >= NSMinX(bbox) - size) { + if (p.y >= NSMaxY(bbox)) { + if (p.y <= NSMaxY(bbox) + size) { + return SouthWestHandle; + } + } else if (p.y <= NSMinY(bbox)) { + if (p.y >= NSMinY(bbox) - size) { + return NorthWestHandle; + } + } else { + float westHandleTop = sideHandleTop(bbox); + if (p.y >= westHandleTop && p.y <= (westHandleTop + size)) { + return WestHandle; + } + } + } + } else if (p.y >= NSMaxY(bbox)) { + if (p.y <= NSMaxY(bbox) + size) { + return SouthHandle; + } + } else if (p.y <= NSMinY(bbox)) { + if (p.y >= NSMinY(bbox) - size) { + return NorthHandle; + } + } + return NoHandle; +} + +- (NSRect) boundingBoxResizeHandleRect:(ResizeHandle)handle { + if (![[self graph] hasBoundingBox]) { + return NSZeroRect; + } + NSRect bbox = [[surface transformer] rectToScreen:[[self graph] boundingBox]]; + switch (handle) { + case EastHandle: + return NSMakeRect(NSMaxX(bbox), sideHandleTop(bbox), size, size); + case SouthEastHandle: + return NSMakeRect(NSMaxX(bbox), NSMaxY(bbox), size, size); + case SouthHandle: + return NSMakeRect(tbHandleLeft(bbox), NSMaxY(bbox), size, size); + case SouthWestHandle: + return NSMakeRect(NSMaxX(bbox), NSMinY(bbox) - size, size, size); + case WestHandle: + return NSMakeRect(NSMinX(bbox) - size, sideHandleTop(bbox), size, size); + case NorthWestHandle: + return NSMakeRect(NSMinX(bbox) - size, NSMinY(bbox) - size, size, size); + case NorthHandle: + return NSMakeRect(tbHandleLeft(bbox), NSMinY(bbox) - size, size, size); + case NorthEastHandle: + return NSMakeRect(NSMinX(bbox) - size, NSMaxY(bbox), size, size); + default: + return NSZeroRect; + } +} + +@end + +@implementation GraphRenderer (Private) +- (BOOL) selectionBoxContainsNode:(Node*)node { + return !NSIsEmptyRect (selectionBox) + && NSPointInRect([[surface transformer] toScreen:[node point]], selectionBox); +} +- (BOOL) halfEdgeIncludesNode:(Node*)node { + if (halfEdgeOrigin == nil) { + return FALSE; + } + return halfEdgeOrigin == node || [self point:halfEdgeEnd hitsNode:node]; +} +- (enum NodeState) nodeState:(Node*)node { + if ([doc isNodeSelected:node]) { + return NodeSelected; + } else if ([self selectionBoxContainsNode:node] || [self halfEdgeIncludesNode:node]) { + return NodeHighlighted; + } else { + return NodeNormal; + } +} + +- (void) renderBoundingBoxWithContext:(id)context { + if ([[self graph] hasBoundingBox]) { + [context saveState]; + + NSRect bbox = [[surface transformer] rectToScreen:[[self graph] boundingBox]]; + + [context setAntialiasMode:AntialiasDisabled]; + [context setLineWidth:1.0]; + [context startPath]; + [context rect:bbox]; + [context strokePathWithColor:MakeSolidRColor (1.0, 0.7, 0.5)]; + + if ([self boundingBoxHandlesShown]) { + [context startPath]; + [context rect:[self boundingBoxResizeHandleRect:EastHandle]]; + [context rect:[self boundingBoxResizeHandleRect:SouthEastHandle]]; + [context rect:[self boundingBoxResizeHandleRect:SouthHandle]]; + [context rect:[self boundingBoxResizeHandleRect:SouthWestHandle]]; + [context rect:[self boundingBoxResizeHandleRect:WestHandle]]; + [context rect:[self boundingBoxResizeHandleRect:NorthWestHandle]]; + [context rect:[self boundingBoxResizeHandleRect:NorthHandle]]; + [context rect:[self boundingBoxResizeHandleRect:NorthEastHandle]]; + [context strokePathWithColor:MakeSolidRColor (0.5, 0.5, 0.5)]; + } + + [context restoreState]; + } +} + +- (void) renderSelectionBoxWithContext:(id)context { + if (!NSIsEmptyRect (selectionBox)) { + [context saveState]; + + [context setAntialiasMode:AntialiasDisabled]; + [context setLineWidth:1.0]; + [context startPath]; + [context rect:selectionBox]; + RColor fColor = MakeRColor (0.8, 0.8, 0.8, 0.2); + RColor sColor = MakeSolidRColor (0.6, 0.6, 0.6); + [context strokePathWithColor:sColor andFillWithColor:fColor]; + + [context restoreState]; + } +} + +- (void) renderImpendingEdgeWithContext:(id)context { + if (halfEdgeOrigin == nil) { + return; + } + [context saveState]; + + [context setLineWidth:1.0]; + [context startPath]; + [context moveTo:halfEdgeOriginPoint]; + [context lineTo:halfEdgeEnd]; + [context strokePathWithColor:MakeRColor (0, 0, 0, 0.5)]; + + [context restoreState]; +} + +- (void) nodeNeedsRefreshing:(NSNotification*)notification { + [self invalidateNode:[[notification userInfo] objectForKey:@"node"]]; +} + +- (void) edgeNeedsRefreshing:(NSNotification*)notification { + Edge *edge = [[notification userInfo] objectForKey:@"edge"]; + NSRect edgeRect = [edge renderedBoundsWithTransformer:[surface transformer] whenSelected:YES]; + edgeRect = NSInsetRect (edgeRect, -2, -2); + [surface invalidateRect:edgeRect]; +} + +- (void) graphNeedsRefreshing:(NSNotification*)notification { + [self invalidateGraph]; +} + +- (void) graphChanged:(NSNotification*)notification { + GraphChange *change = [[notification userInfo] objectForKey:@"change"]; + switch ([change changeType]) { + case GraphAddition: + case GraphDeletion: + [self invalidateNodes:[change affectedNodes]]; + [self invalidateEdges:[change affectedEdges]]; + break; + case NodePropertyChange: + if (!NSEqualPoints ([[change oldNode] point], [[change nwNode] point])) { + // if the node has moved, it may be affecting edges + [surface invalidate]; + } else { + // invalide both old and new (old node may be larger) + [self invalidateNode:[change oldNode]]; + [self invalidateNode:[change nwNode]]; + } + break; + case EdgePropertyChange: + // invalide both old and new (old bend may increase bounds) + [self invalidateEdge:[change oldEdge]]; + [self invalidateEdge:[change nwEdge]]; + [self invalidateEdge:[change edgeRef]]; + break; + case NodesPropertyChange: + { + NSEnumerator *enumerator = [[change oldNodeTable] keyEnumerator]; + Node *node = nil; + while ((node = [enumerator nextObject]) != nil) { + NSPoint oldPos = [[[change oldNodeTable] objectForKey:node] point]; + NSPoint newPos = [[[change nwNodeTable] objectForKey:node] point]; + if (NSEqualPoints (oldPos, newPos)) { + [self invalidateNode:[[change oldNodeTable] objectForKey:node]]; + [self invalidateNode:[[change nwNodeTable] objectForKey:node]]; + } else { + [surface invalidate]; + break; + } + } + } + break; + case NodesShift: + case NodesFlip: + case BoundingBoxChange: + [surface invalidate]; + break; + default: + // unknown change + [surface invalidate]; + break; + }; +} + +- (void) nodeStylePropertyChanged:(NSNotification*)notification { + if (![@"name" isEqual:[[notification userInfo] objectForKey:@"propertyName"]]) { + BOOL affected = NO; + for (Node *node in [[self graph] nodes]) { + if ([node style] == [notification object]) + affected = YES; + } + if (affected) + [surface invalidate]; + } +} + +- (void) edgeStylePropertyChanged:(NSNotification*)notification { + if (![@"name" isEqual:[[notification userInfo] objectForKey:@"propertyName"]]) { + BOOL affected = NO; + for (Edge *edge in [[self graph] edges]) { + if ([edge style] == [notification object]) + affected = YES; + } + if (affected) + [surface invalidate]; + } +} + +@end + +// vim:ft=objc:ts=8:et:sts=4:sw=4 -- cgit v1.2.3