]> granicus.if.org Git - graphviz/commitdiff
graph display updates when underlying file is changed; GVWindowController and GVAttri...
authorglenlow <devnull@localhost>
Tue, 27 May 2008 06:50:13 +0000 (06:50 +0000)
committerglenlow <devnull@localhost>
Tue, 27 May 2008 06:50:13 +0000 (06:50 +0000)
macosx/GVFileNotificationCenter.h [new file with mode: 0644]
macosx/GVFileNotificationCenter.m [new file with mode: 0644]

diff --git a/macosx/GVFileNotificationCenter.h b/macosx/GVFileNotificationCenter.h
new file mode 100644 (file)
index 0000000..3e0f2af
--- /dev/null
@@ -0,0 +1,33 @@
+/* $Id$ $Revision$ */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+*      This software is part of the graphviz package      *
+*                http://www.graphviz.org/                 *
+*                                                         *
+*            Copyright (c) 1994-2008 AT&T Corp.           *
+*                and is licensed under the                *
+*            Common Public License, Version 1.0           *
+*                      by AT&T Corp.                      *
+*                                                         *
+*        Information and Software Systems Research        *
+*              AT&T Research, Florham Park NJ             *
+**********************************************************/
+
+#import <Foundation/Foundation.h>
+
+@interface GVFileNotificationCenter : NSObject
+{
+       CFFileDescriptorRef _queue;
+       NSMutableSet *_records;
+}
+
++ (void)initialize;
++ (id)defaultCenter;
+
+- (id)init;
+
+- (void)addObserver:(id)observer selector:(SEL)selector path:(NSString *)path;
+- (void)removeObserver:(id)observer path:(NSString *)path;
+
+@end
diff --git a/macosx/GVFileNotificationCenter.m b/macosx/GVFileNotificationCenter.m
new file mode 100644 (file)
index 0000000..9288d6d
--- /dev/null
@@ -0,0 +1,170 @@
+/* $Id$ $Revision$ */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+*      This software is part of the graphviz package      *
+*                http://www.graphviz.org/                 *
+*                                                         *
+*            Copyright (c) 1994-2008 AT&T Corp.           *
+*                and is licensed under the                *
+*            Common Public License, Version 1.0           *
+*                      by AT&T Corp.                      *
+*                                                         *
+*        Information and Software Systems Research        *
+*              AT&T Research, Florham Park NJ             *
+**********************************************************/
+
+#include <fcntl.h>
+#include <sys/event.h>
+
+#import "GVFileNotificationCenter.h"
+
+static GVFileNotificationCenter *_defaultCenter = nil;
+
+@interface GVFileNotificationRecord : NSObject
+{
+       id _observer;
+       NSString *_path;
+       SEL _selector;
+       int _fileDescriptor;
+}
+
+@property(readonly) id observer;
+@property(readonly) NSString *path;
+@property SEL selector;
+@property int fileDescriptor;
+
+@end
+
+@implementation GVFileNotificationRecord
+
+@synthesize observer = _observer;
+@synthesize path = _path;
+@synthesize selector = _selector;
+@synthesize fileDescriptor = _fileDescriptor;
+
+- (id)initWithObserver:(id)observer path:(NSString *)path
+{
+       if (self = [super init]) {
+               _observer = observer;
+               _path = [path retain];
+       }
+       return self;
+}
+
+- (BOOL)isEqual:(id)anObject
+{
+       /* if the other object is one of us, compare observers + paths only */
+       return [anObject isKindOfClass:[GVFileNotificationRecord class]] ? [self observer] == [anObject observer] && [[self path] isEqualToString:[anObject path]] : NO;
+}
+
+- (NSUInteger)hash
+{
+       /* hash based on observers + paths only */
+       return (NSUInteger)_observer ^ [_path hash];
+}
+
+- (void)dealloc
+{
+       [_path release];
+       [super dealloc];
+}
+
+@end
+
+static void noteFileChanged(CFFileDescriptorRef queue, CFOptionFlags callBackTypes, void *info)
+{
+       int queueDescriptor = CFFileDescriptorGetNativeDescriptor(queue);
+    
+       /* grab the next event from the kernel queue */
+       struct kevent event;
+    kevent(queueDescriptor, NULL, 0, &event, 1, NULL);
+       
+       GVFileNotificationRecord *record = (GVFileNotificationRecord *)event.udata;
+       if (record) {
+               /* report the file change to the observer */
+               [record.observer performSelector:record.selector withObject:record.path];
+
+               /* if watched file is deleted, try to reopen the file and watch it again */
+               if (event.fflags & NOTE_DELETE) {
+                       close(record.fileDescriptor);
+                       
+                       int fileDescriptor = open([record.path UTF8String], O_EVTONLY);
+                       if (fileDescriptor != -1) {
+                               struct kevent change;
+                               EV_SET (&change, fileDescriptor, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND, 0, record);
+                               if (kevent (queueDescriptor, &change, 1, NULL, 0, NULL) != -1)
+                                       record.fileDescriptor = fileDescriptor;
+                       }
+               }
+               
+       }
+       
+       /* reenable the callbacks (they were automatically disabled when handling the CFFileDescriptor) */
+    CFFileDescriptorEnableCallBacks(queue, kCFFileDescriptorReadCallBack);
+}
+
+
+@implementation GVFileNotificationCenter
+
++ (void)initialize
+{
+       if (!_defaultCenter)
+               _defaultCenter = [[GVFileNotificationCenter alloc] init];
+}
+
++ (id)defaultCenter
+{
+       return _defaultCenter;
+}
+
+- (id)init
+{
+       if (self = [super init]) {
+               /* create kernel queue, wrap a CFFileDescriptor around it and schedule it on the Cocoa run loop */
+               _queue = CFFileDescriptorCreate(kCFAllocatorDefault, kqueue(), true, noteFileChanged, NULL);
+               CFFileDescriptorEnableCallBacks(_queue, kCFFileDescriptorReadCallBack);
+               CFRunLoopAddSource(
+                       [[NSRunLoop currentRunLoop] getCFRunLoop],
+                       CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, _queue, 0),
+                       kCFRunLoopDefaultMode);
+                       
+               /* need to keep track of observers */
+               _records = [[NSMutableSet alloc] init];
+       }
+       return self;
+}
+
+- (void)addObserver:(id)observer selector:(SEL)selector path:(NSString *)path
+{
+       GVFileNotificationRecord *record = [[[GVFileNotificationRecord alloc] initWithObserver:observer path:path] autorelease];
+       GVFileNotificationRecord *oldRecord = [_records member:record];
+       
+       if (oldRecord)
+               /* record already exists, just update the selector */
+               oldRecord.selector = selector;
+       else {
+               /* new record, start monitoring the path */
+               int fileDescriptor = open([path UTF8String], O_EVTONLY);
+               if (fileDescriptor != -1) {
+                       struct kevent change;
+                       EV_SET (&change, fileDescriptor, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_DELETE | NOTE_WRITE | NOTE_EXTEND, 0, record);
+                       if (kevent (CFFileDescriptorGetNativeDescriptor(_queue), &change, 1, NULL, 0, NULL) != -1) {
+                               record.selector = selector;
+                               record.fileDescriptor = fileDescriptor;
+                               [_records addObject:record];
+                       }
+               }
+       }
+}
+
+- (void)removeObserver:(id)observer path:(NSString *)path
+{
+       GVFileNotificationRecord *record = [_records member:[[[GVFileNotificationRecord alloc] initWithObserver:observer path:path] autorelease]];
+       if (record) {
+               close(record.fileDescriptor);   /* closing the file descriptor also removes it from the kqueue */
+               [_records removeObject:record];
+       }
+       
+}
+@end