/************************************************************************* * Copyright (c) 2011 AT&T Intellectual Property * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * https://www.eclipse.org/legal/epl-v10.html * * Contributors: Details at http://www.graphviz.org/ *************************************************************************/ #import "GVZGraph.h" #import "GVGraphArguments.h" #import "GVGraphDefaultAttributes.h" NSString *const GVGraphvizErrorDomain = @"GVGraphvizErrorDomain"; extern double PSinputscale; static GVC_t *_graphContext = nil; @implementation GVZGraph @synthesize graph = _graph; @synthesize arguments = _arguments; @synthesize graphAttributes = _graphAttributes; @synthesize defaultNodeAttributes = _defaultNodeAttributes; @synthesize defaultEdgeAttributes = _defaultEdgeAttributes; extern char *gvplugin_list(GVC_t * gvc, api_t api, const char *str); + (void)initialize { _graphContext = gvContext(); } + (NSArray *)pluginsWithAPI:(api_t)api { NSMutableSet *plugins = [NSMutableSet set]; /* go through each non-empty plugin in the list, ignoring the package part */ char *pluginList = gvplugin_list(_graphContext, api, ":"); char *restOfPlugins; char *nextPlugin; for (restOfPlugins = pluginList; (nextPlugin = strsep(&restOfPlugins, " "));) { if (*nextPlugin) { char *lastColon = strrchr(nextPlugin, ':'); if (lastColon) { *lastColon = '\0'; [plugins addObject:[NSString stringWithCString:nextPlugin encoding:NSUTF8StringEncoding]]; } } } free(pluginList); return [[plugins allObjects] sortedArrayUsingSelector:@selector(compare:)]; } - (id)initWithURL:(NSURL *)URL error:(NSError **)outError { char *parentDir,*ptr; if (self = [super init]) { if ([URL isFileURL]) { /* open a FILE* on the file URL */ FILE *file = fopen([[URL path] fileSystemRepresentation], "r"); if (!file) { if (outError) *outError = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; [self autorelease]; return nil; } _graph = agread(file,0); if (!_graph) { if (outError) *outError = [NSError errorWithDomain:GVGraphvizErrorDomain code:GVFileParseError userInfo:nil]; [self autorelease]; return nil; } if(!agget(_graph,"imagepath")){ parentDir = (char *)[[URL path] fileSystemRepresentation]; ptr = strrchr(parentDir,'/'); *ptr = 0; agattr(_graph,AGRAPH,"imagepath",parentDir); } fclose(file); } else { /* read the URL into memory */ NSMutableData *memory = [NSMutableData dataWithContentsOfURL:URL options:0 error:outError]; if (!memory) { [self autorelease]; return nil; } /* null terminate the data */ char nullByte = '\0'; [memory appendBytes:&nullByte length:1]; _graph = agmemread((char*)[memory bytes]); if (!_graph) { if (outError) *outError = [NSError errorWithDomain:GVGraphvizErrorDomain code:GVFileParseError userInfo:nil]; [self autorelease]; return nil; } } _freeLastLayout = NO; _arguments = [[GVGraphArguments alloc] initWithGraph:self]; _graphAttributes = [[GVGraphDefaultAttributes alloc] initWithGraph:self prototype:AGRAPH]; _defaultNodeAttributes = [[GVGraphDefaultAttributes alloc] initWithGraph:self prototype:AGNODE]; _defaultEdgeAttributes = [[GVGraphDefaultAttributes alloc] initWithGraph:self prototype:AGEDGE]; } return self; } - (BOOL)writeToURL:(NSURL *)URL error:(NSError **)outError { if ([URL isFileURL]) { /* open a FILE* on the file URL */ FILE *file = fopen([[URL path] fileSystemRepresentation], "w"); if (!file) { if (outError) *outError = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; return NO; } /* write it out */ if (agwrite(_graph, file) != 0) { if (outError) *outError = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]; return NO; } fclose(file); return YES; } else /* can't write out to non-file URL */ return NO; } - (void)noteChanged:(BOOL)relayout { /* if we need to layout, apply globals and then relayout */ if (relayout) { NSString* layout = [_arguments objectForKey:@"layout"]; if (layout) { if (_freeLastLayout) gvFreeLayout(_graphContext, _graph); /* apply scale */ NSString* scale = [_arguments objectForKey:@"scale"]; PSinputscale = scale ? [scale doubleValue] : 0.0; if (PSinputscale == 0.0) PSinputscale = 72.0; if (gvLayout(_graphContext, _graph, (char*)[layout UTF8String]) != 0) @throw [NSException exceptionWithName:@"GVException" reason:@"bad layout" userInfo:nil]; _freeLastLayout = YES; } } [[NSNotificationCenter defaultCenter] postNotificationName: @"GVGraphDidChange" object:self]; } - (NSData*)renderWithFormat:(NSString *)format { char *renderedData = NULL; unsigned int renderedLength = 0; if (gvRenderData(_graphContext, _graph, (char*)[format UTF8String], &renderedData, &renderedLength) != 0) @throw [NSException exceptionWithName:@"GVException" reason:@"bad render" userInfo:nil]; return [NSData dataWithBytesNoCopy:renderedData length:renderedLength freeWhenDone:YES]; } - (void)renderWithFormat:(NSString *)format toURL:(NSURL *)URL { if ([URL isFileURL]) { if (gvRenderFilename(_graphContext, _graph, (char*)[format UTF8String], (char*)[[URL path] UTF8String]) != 0) @throw [NSException exceptionWithName:@"GVException" reason:@"bad render" userInfo:nil]; } else [[self renderWithFormat:format] writeToURL:URL atomically:NO]; } - (void)dealloc { if (_graph) agclose(_graph); [_arguments release]; [_graphAttributes release]; [_defaultNodeAttributes release]; [_defaultEdgeAttributes release]; [super dealloc]; } @end