/* FScriptAppController.m Copyright 1998-2009 Philippe Mougin. */ /* This software is open source. See the license. */ #import "FScriptAppController.h" #import #import #import "FSInterpreter.h" #import "FSInterpreterView.h" #import "FSSystem.h" #import "FSBlock.h" #import "FSArray.h" #import "FSNSString.h" #import #import "FSServicesProvider.h" #import "FSMiscTools.h" #import "FSDemoAssistant.h" #import #import extern char **environ; NSString *findPathToFileInLibraryWithinUserDomain(NSString *fileName) /*" Returns the path to the first occurrence of fileName in a Library directory within the User domain. "*/ { NSString *result = nil; // the returned path NSString *candidate; // candidate paths NSArray *pathArray; // array of standard locations NSEnumerator *pathEnumerator; // used to enumerate pathArray pathArray = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); pathEnumerator = [pathArray objectEnumerator]; while(nil == result && (nil != (candidate = [pathEnumerator nextObject]))) { result = [candidate stringByAppendingPathComponent:fileName]; if(![[NSFileManager defaultManager] fileExistsAtPath:result]) { result = nil; } } return result; } NSString *findPathToFileInLibraryWithinSystemDomain(NSString *fileName) /*" Returns the path to the first occurrence of fileName in a Library directory within the System domain. "*/ { NSString *result = nil; // the returned path NSString *candidate; // candidate paths NSArray *pathArray; // array of standard locations NSEnumerator *pathEnumerator; // used to enumerate pathArray pathArray = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSSystemDomainMask, YES); pathEnumerator = [pathArray objectEnumerator]; while(nil == result && (nil != (candidate = [pathEnumerator nextObject]))) { result = [candidate stringByAppendingPathComponent:fileName]; if(![[NSFileManager defaultManager] fileExistsAtPath:result]) { result = nil; } } return result; } @interface NSUserDefaults(FSNSUserDefaults) - (void)setObject:(id)value forKey:(NSString *)defaultName inDomain:(NSString *)domainName; @end @implementation FScriptAppController + (void)initialize { NSMutableDictionary *registrationDict = [NSMutableDictionary dictionary]; [registrationDict setObject:[NSNumber numberWithDouble:[[NSFont userFixedPitchFontOfSize:-1] pointSize]] forKey:@"FScriptFontSize"]; [registrationDict setObject:@"NO" forKey:@"FScriptShouldJournal"]; [registrationDict setObject:@"NO" forKey:@"FScriptConfirmWhenQuitting"]; [registrationDict setObject:@"YES" forKey:@"FScriptDisplayObjectBrowserAtLaunchTime"]; [registrationDict setObject:@"YES" forKey:@"FScriptAutomaticallyIntrospectDeclaredProperties"]; [registrationDict setObject:@"NO" forKey:@"FScriptShowDemoAssistant"]; [registrationDict setObject:@"NO" forKey:@"FScriptLoadSystemFrameworks"]; [registrationDict setObject:@"NO" forKey:@"FScriptLoadPrivateSystemFrameworks"]; [[NSUserDefaults standardUserDefaults] registerDefaults:registrationDict]; } - (void)loadSystemFrameworks // Contributed by Cedric Luthi { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSMutableArray *systemFrameworksPaths = [NSMutableArray arrayWithObject:@"/System/Library/Frameworks"]; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"FScriptLoadPrivateSystemFrameworks"]) [systemFrameworksPaths addObject:@"/System/Library/PrivateFrameworks"]; for (NSString *systemFrameworksPath in systemFrameworksPaths) { for (NSString *framework in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:systemFrameworksPath error:NULL]) { NSBundle *frameworkBundle = [NSBundle bundleWithPath:[systemFrameworksPath stringByAppendingPathComponent:framework]]; if ([frameworkBundle preflightAndReturnError:nil]) [frameworkBundle load]; } } [pool drain]; } - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { NSFileManager *fileManager = [NSFileManager defaultManager]; BOOL b = NO; NSString *latent; NSString *latentPath; NSString *repositoryPath = [[NSUserDefaults standardUserDefaults] stringForKey:@"FScriptRepositoryPath"]; FSServicesProvider *servicesProvider; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"FScriptLoadSystemFrameworks"]) [self loadSystemFrameworks]; if (floor(NSAppKitVersionNumber) > 949) { // 10.6 or later system NSString *systemFrameworksDirectoryPath; NSString *path; systemFrameworksDirectoryPath = findPathToFileInLibraryWithinSystemDomain(@"Frameworks"); if (systemFrameworksDirectoryPath) { path = [systemFrameworksDirectoryPath stringByAppendingPathComponent:@"PreferencePanes.framework"]; [[NSBundle bundleWithPath:path] load]; path = [systemFrameworksDirectoryPath stringByAppendingPathComponent:@"ScreenSaver.framework"]; [[NSBundle bundleWithPath:path] load]; path = [systemFrameworksDirectoryPath stringByAppendingPathComponent:@"CoreLocation.framework"]; [[NSBundle bundleWithPath:path] load]; path = [systemFrameworksDirectoryPath stringByAppendingPathComponent:@"CoreWLAN.framework"]; [[NSBundle bundleWithPath:path] load]; path = [systemFrameworksDirectoryPath stringByAppendingPathComponent:@"ImageCaptureCore.framework"]; [[NSBundle bundleWithPath:path] load]; path = [systemFrameworksDirectoryPath stringByAppendingPathComponent:@"OpenDirectory.framework"]; [[NSBundle bundleWithPath:path] load]; path = [systemFrameworksDirectoryPath stringByAppendingPathComponent:@"ServerNotification.framework"]; [[NSBundle bundleWithPath:path] load]; } } // if (!repositoryPath || ![fileManager fileExistsAtPath:repositoryPath isDirectory:&b]) { NSError *error = nil; NSString *applicationSupportDirectoryPath = findPathToFileInLibraryWithinUserDomain(@"Application Support"); BOOL repositoryCreated = NO; if (!applicationSupportDirectoryPath) { NSArray *pathArray = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); if ([pathArray count] > 0) [fileManager createDirectoryAtPath:[[pathArray objectAtIndex:0] stringByAppendingPathComponent:@"Application Support"] withIntermediateDirectories:NO attributes:nil error:NULL]; } applicationSupportDirectoryPath = findPathToFileInLibraryWithinUserDomain(@"Application Support"); if (applicationSupportDirectoryPath) { repositoryPath = [applicationSupportDirectoryPath stringByAppendingPathComponent:@"F-Script"]; [fileManager createDirectoryAtPath:[repositoryPath stringByAppendingPathComponent:@"classes"] withIntermediateDirectories:YES attributes:nil error:&error]; if (error) NSLog(@"Failed to create the repository: %@", error); else { repositoryCreated = YES; if ([[NSUserDefaults standardUserDefaults] respondsToSelector:@selector(setObject:forKey:inDomain:)]) [[NSUserDefaults standardUserDefaults] setObject:repositoryPath forKey:@"FScriptRepositoryPath" inDomain:NSGlobalDomain]; // This is an undocumented Cocoa API in Mac OS X 10.1 else [[NSUserDefaults standardUserDefaults] setObject:repositoryPath forKey:@"FScriptRepositoryPath"]; [[NSUserDefaults standardUserDefaults] setObject:[repositoryPath stringByAppendingPathComponent:@"journal.txt"] forKey:@"FScriptJournalName"]; [[NSUserDefaults standardUserDefaults] synchronize]; } } else { NSLog(@"Failed to create the repository in the user's \"Application Support\" directory."); } if (!repositoryCreated) { NSInteger choice = NSRunAlertPanel(@"Instalation" , @"F-Script is about to create a directory named \"FScriptRepository\" in your home directory. This directory will be used as a repository for things like extension bundles for F-Script and a journal file.", @"create the repository", @"don't create the repository", @"create the repository elsewhere..."); if (choice == NSAlertOtherReturn || choice == NSAlertDefaultReturn) { if (choice == NSAlertOtherReturn) { NSOpenPanel *openPanel = [NSOpenPanel openPanel]; [openPanel setCanChooseFiles:NO]; [openPanel setCanChooseDirectories:YES]; [openPanel setTitle:@"Choose the directory that will become the F-Script repository"]; if([openPanel runModal] == NSOKButton) repositoryPath = [openPanel filename]; else repositoryPath = nil; } else repositoryPath = [NSHomeDirectory() stringByAppendingPathComponent:@"FScriptRepository"]; error = nil; [fileManager createDirectoryAtPath:[repositoryPath stringByAppendingPathComponent:@"classes"] withIntermediateDirectories:YES attributes:nil error:&error]; if (error) NSLog(@"Failed to create the repository: %@", error); else { repositoryCreated = YES; if ([[NSUserDefaults standardUserDefaults] respondsToSelector:@selector(setObject:forKey:inDomain:)]) [[NSUserDefaults standardUserDefaults] setObject:repositoryPath forKey:@"FScriptRepositoryPath" inDomain:NSGlobalDomain]; // This is an undocumented Cocoa API in Mac OS X 10.1 else [[NSUserDefaults standardUserDefaults] setObject:repositoryPath forKey:@"FScriptRepositoryPath"]; [[NSUserDefaults standardUserDefaults] setObject:[repositoryPath stringByAppendingPathComponent:@"journal.txt"] forKey:@"FScriptJournalName"]; [[NSUserDefaults standardUserDefaults] synchronize]; } } } } else if (!(b && [fileManager isWritableFileAtPath:repositoryPath])) // partial consistency check { NSLog(@"fatal problem: the repository file \"%@\" is not a directory or is not writable", repositoryPath); exit(1); } // Initialize the random number generator with random seeds srandomdev(); srand48(random()); // We will catch most unhandled run-time error with this. [[NSExceptionHandler defaultExceptionHandler] setExceptionHandlingMask:63]; // We initialize the journaling system [[interpreterView interpreter] setShouldJournal:[[NSUserDefaults standardUserDefaults] boolForKey:@"FScriptShouldJournal"]]; if (repositoryPath) [[NSUserDefaults standardUserDefaults] setObject:[repositoryPath stringByAppendingPathComponent:@"journal.txt"] forKey:@"FScriptJournalName"]; [[interpreterView interpreter] setJournalName:[[NSUserDefaults standardUserDefaults] stringForKey:@"FScriptJournalName"]]; // // JG servicesProvider = [[FSServicesProvider alloc] initWithFScriptInterpreterViewProvider:self]; [servicesProvider registerExports]; // // // Latent block processing latentPath = [[[NSUserDefaults standardUserDefaults] stringForKey:@"FScriptRepositoryPath"] stringByAppendingPathComponent:@"fs_latent"]; if (latentPath && [[NSFileManager defaultManager] fileExistsAtPath:latentPath]) { NSStringEncoding usedEncoding; NSError *error; latent = [NSString stringWithContentsOfFile:latentPath usedEncoding:&usedEncoding error:&error]; if (!latent) { NSLog(@"Unable to read the latent block: %@", [error localizedDescription]); } else { BOOL found; FSInterpreter *interpreter = [interpreterView interpreter]; FSSystem *sys = [interpreter objectForIdentifier:@"sys" found:&found]; FSBlock *bl; NSAssert(found,@"F-Script internal error: symbol \"sys\" not defined"); @try { bl = [sys blockFromString:latent]; [bl value]; } @catch (id exception) { [interpreterView notifyUser:[NSString stringWithFormat:@"Error in the latent block (file %@): %@",latentPath, FSErrorMessageFromException(exception)]]; } } } //---------- [[interpreterView window] makeKeyAndOrderFront:nil]; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"FScriptDisplayObjectBrowserAtLaunchTime"]) [[interpreterView interpreter] browse]; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"FScriptShowDemoAssistant"]) [[[FSDemoAssistant alloc] initWithInterpreterView:interpreterView] activate]; } - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { [self performSelector:@selector(openFile:) withObject:filename afterDelay:0]; return YES; } - (void)openFile:(NSString *)filename { { // generates a filename with a unix style path separator FSArray *elems = [filename asArray]; NSUInteger i, nb; for (i = 0, nb = [elems count]; i < nb; i++) if ([[elems objectAtIndex:i] isEqual:@"\\"]) [elems replaceObjectAtIndex:i withObject:@"/"]; filename = [elems operator_backslash:[@"#++" asBlock]]; } if ([[filename pathExtension] isEqualToString:@"space"]) [interpreterView putCommand:[NSString stringWithFormat:@"sys loadSpace:%@\n",filename]]; else { NSString *fname = [filename lastPathComponent]; NSUInteger nb = [fname length]; while (nb != ([fname = [fname stringByDeletingPathExtension] length])) nb = [fname length]; // remove all the extentions [interpreterView putCommand:[NSString stringWithFormat:@"%@ := sys load:%@",fname,filename]]; } } - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { if ([[NSUserDefaults standardUserDefaults] boolForKey:@"FScriptConfirmWhenQuitting"] && !quitConfirmed) { NSInteger choice = NSRunAlertPanel(@"QUIT", @"Are you sure you want to quit F-Script?", @"Quit", @"Cancel", nil,nil); if (choice == NSAlertDefaultReturn) return NSTerminateNow; else return NSTerminateCancel; // don't quit } else return NSTerminateNow; } - (void)dealloc { if ((id)[[NSApplication sharedApplication] delegate] == self) [[NSApplication sharedApplication] setDelegate:nil]; // since we don't retain outlets infoPanel and interpreterView, we don't have to release them here. [showConsoleMenuItem release]; [super dealloc]; } - (id) init { self = [super init]; if (self != nil) { showConsoleMenuItem = [[NSMenuItem alloc] initWithTitle:@"F-Script" action:@selector(showConsole:) keyEquivalent:@""]; quitConfirmed = NO; } return self; } - (id)interpreterView // For use by JG FSServiceProvider { return interpreterView; } - (void)newDemoAssistant:(id)sender { [[[FSDemoAssistant alloc] initWithInterpreterView:interpreterView] activate]; } - (void)newObjectBrowser:sender { [[interpreterView interpreter] browse]; } - (void)showConsole:(id)sender { NSMenu *windowMenu = [[[NSApp mainMenu] itemWithTitle:@"Window"] submenu]; [[interpreterView window] makeKeyAndOrderFront:nil]; [windowMenu removeItem:showConsoleMenuItem]; } - (void)showInfoPanel:(id)sender { /*if (!infoPanel) { if (![NSBundle loadNibNamed:@"FScriptAppInfo" owner:self]) { NSLog(@"Failed to load FScriptAppInfo.nib"); NSBeep(); return; } [infoPanel center]; } [infoPanel makeKeyAndOrderFront:nil]; */ NSMutableAttributedString *s = [[[NSMutableAttributedString alloc] initWithString:@"http://www.fscript.org" attributes:[NSDictionary dictionaryWithObject:@"http://www.fscript.org" forKey:NSLinkAttributeName]] autorelease]; [NSApp orderFrontStandardAboutPanelWithOptions:[NSDictionary dictionaryWithObject:s forKey:@"Credits"]]; } - (void)showPreferencePanel:(id)sender { if (!preferencePanel) { if (![NSBundle loadNibNamed:@"FScriptAppPreference" owner:self]) { NSLog(@"Failed to load FScriptAppPreference.nib"); NSBeep(); return; } [preferencePanel center]; } [fontSizeUI setDoubleValue:[interpreterView fontSize]]; [shouldJournalUI setState:[[interpreterView interpreter] shouldJournal]]; [confirmWhenQuittingUI setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"FScriptConfirmWhenQuitting"]]; [displayObjectBrowserAtLaunchTimeUI setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"FScriptDisplayObjectBrowserAtLaunchTime"]]; [automaticallyIntrospectDeclaredPropertiesUI setState:[[NSUserDefaults standardUserDefaults] boolForKey:@"FScriptAutomaticallyIntrospectDeclaredProperties"]]; [preferencePanel makeKeyAndOrderFront:nil]; } - (void)updatePreference:(id)sender // action { //NSLog(@"** updatePreference"); if (sender == fontSizeUI) { [[NSUserDefaults standardUserDefaults] setDouble:[fontSizeUI doubleValue] forKey:@"FScriptFontSize"]; [interpreterView setFontSize:[fontSizeUI doubleValue]]; } else if (sender == shouldJournalUI) { [[NSUserDefaults standardUserDefaults] setBool:[shouldJournalUI state] forKey:@"FScriptShouldJournal"]; [[interpreterView interpreter] setShouldJournal:[shouldJournalUI state]]; } else if (sender == confirmWhenQuittingUI) { [[NSUserDefaults standardUserDefaults] setBool:[confirmWhenQuittingUI state] forKey:@"FScriptConfirmWhenQuitting"]; } else if (sender == displayObjectBrowserAtLaunchTimeUI) { [[NSUserDefaults standardUserDefaults] setBool:[displayObjectBrowserAtLaunchTimeUI state] forKey:@"FScriptDisplayObjectBrowserAtLaunchTime"]; } else if (sender == automaticallyIntrospectDeclaredPropertiesUI) { [[NSUserDefaults standardUserDefaults] setBool:[automaticallyIntrospectDeclaredPropertiesUI state] forKey:@"FScriptAutomaticallyIntrospectDeclaredProperties"]; } } ////////// Window delegate methods (I'm the delegate of the the console window) - (void)windowWillClose:(NSNotification *)aNotification { NSMenu *windowMenu = [[[NSApp mainMenu] itemWithTitle:@"Window"] submenu]; [windowMenu insertItem:showConsoleMenuItem atIndex:[windowMenu numberOfItems]]; } @end