/* * R.app : a Cocoa front end to: "R A Computer Language for Statistical Data Analysis" * * R.app Copyright notes: * Copyright (C) 2004-5 The R Foundation * written by Stefano M. Iacus and Simon Urbanek * * * R Copyright notes: * Copyright (C) 1995-1996 Robert Gentleman and Ross Ihaka * Copyright (C) 1998-2001 The R Development Core Team * Copyright (C) 2002-2004 The R Foundation * * 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. * * A copy of the GNU General Public License is available via WWW at * http://www.gnu.org/copyleft/gpl.html. You can also obtain it by * writing to the Free Software Foundation, Inc., 59 Temple Place, * Suite 330, Boston, MA 02111-1307 USA. */ #import "RGUI.h" #import "RDocument.h" #import "RDocumentController.h" #import "RDocumentWinCtrl.h" #import "RController.h" #import "Preferences.h" #import "RChooseEncodingPopupAccessory.h" #import "REngine.h" #import "HelpManager.h" #import "NSString_RAdditions.h" // R defines "error" which is deadly as we use open ... with ... error: where error then gets replaced by Rf_error #ifdef error #undef error #endif @implementation RDocument - (id)init { self = [super init]; if (self) { SLog(@"RDocument(%@) init", self); documentEncoding = NSUTF8StringEncoding; initialContents=nil; initialContentsType=nil; isEditable=YES; isREdit=NO; myWinCtrl=nil; rdToolsAreWorking=NO; fileTypeWasChangedWhileSaving = NO; } return self; } - (void)close { SLog(@"RDocument.close <%@> (wctrl=%@)", self, myWinCtrl); if (initialContents) [initialContents release], initialContents=nil; if (initialContentsType) [initialContentsType release], initialContentsType=nil; if (myWinCtrl) { SLog(@" - window: %@", [myWinCtrl window]); [self removeWindowController:myWinCtrl]; [myWinCtrl close]; // --- something is broken - winctrl close doesn't work - I have no idea why - this is a horrible hack to cover up //NSWindow *w = [myWinCtrl window]; //if (w) [NSApp removeWindowsItem: w]; //[[(RDocumentController*)[NSDocumentController sharedDocumentController] walkKeyListBack] makeKeyAndOrderFront:self]; // --- end of hack [myWinCtrl release]; myWinCtrl=nil; } [super close]; } - (void)dealloc { if (myWinCtrl) [self close]; [super dealloc]; } // FIXME: I don't like this - we should use common text storage instead; conceptually textView is NOT the storage part - (NSTextView *)textView { return [myWinCtrl textView]; } - (void) makeWindowControllers { SLog(@"RDocument.makeWindowControllers: creating RDocumentWinCtrl"); if (myWinCtrl) { SLog(@"*** RDocument.makeWindowControllers: my assumption is that I have only one win controller, but I already have %@! I'll autorelease the first one but won't detach it - don't blame me if this crashes...", myWinCtrl); [myWinCtrl autorelease]; } // create RDocumentWinCtrl which is a window controller - it loads the corresponding NIB and sets up the window myWinCtrl = [[RDocumentWinCtrl alloc] initWithWindowNibName:@"RDocument"]; [self addWindowController:myWinCtrl]; } - (NSString*)windowNibName { return @"RDocument"; } - (int) fileEncoding { return (int) documentEncoding; } - (void) setFileEncoding: (int) encoding { SLog(@" - setFileEncoding: %d", encoding); documentEncoding = (NSStringEncoding) encoding; } - (void)didSaveSelector { // Reopen file if file type was changed while saving if(fileTypeWasChangedWhileSaving) { NSError *theError = nil; fileTypeWasChangedWhileSaving = NO; [myWinCtrl close]; [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:[self fileURL] display:YES error:&theError]; if (theError) { NSLog(@"*** openDocumentWithContentsOfURL: failed with %@", theError); NSBeep(); } return; } // Remain focus on current document after closing SaveAs panel [[myWinCtrl window] makeKeyWindow]; encodingPopUp = nil; } - (void)runModalSavePanelForSaveOperation:(NSSaveOperationType)saveOperation delegate:(id)delegate didSaveSelector:(SEL)didSaveSelector contextInfo:(void *)contextInfo { // dispatch didSaveSelector: in order to remain input focus to current document [super runModalSavePanelForSaveOperation:saveOperation delegate:self didSaveSelector:@selector(didSaveSelector) contextInfo:contextInfo]; } // customize Save panel by adding "encoding" view for R documents - (BOOL)prepareSavePanel:(NSSavePanel *)savePanel { if([[self fileType] isEqualToString:ftRSource]) { [savePanel setAllowedFileTypes:[NSArray arrayWithObject:@"R"]]; if (myWinCtrl) [savePanel setAccessoryView:[[[NSDocumentController sharedDocumentController] class] encodingAccessory:(NSStringEncoding)documentEncoding includeDefaultEntry:NO encodingPopUp:&encodingPopUp]]; if(encodingPopUp) [encodingPopUp setEnabled:YES]; [savePanel setAllowsOtherFileTypes:YES]; } else if([[self fileType] isEqualToString:ftRdDoc]) { [savePanel setAllowedFileTypes:[NSArray arrayWithObject:@"Rd"]]; if (myWinCtrl) [savePanel setAccessoryView:[[[NSDocumentController sharedDocumentController] class] encodingAccessory:(NSStringEncoding)documentEncoding includeDefaultEntry:NO encodingPopUp:&encodingPopUp]]; if(encodingPopUp) [encodingPopUp setEnabled:YES]; [savePanel setAllowsOtherFileTypes:YES]; } else if(initialContentsType && [initialContentsType hasSuffix:@".rtf"]) { [savePanel setAllowedFileTypes:[NSArray arrayWithObject:@"rtf"]]; [savePanel setAllowsOtherFileTypes:NO]; [savePanel setAccessoryView:nil]; } [savePanel setCanSelectHiddenExtension:YES]; return YES; } - (BOOL)writeToURL:(NSURL *)absoluteURL ofType:(NSString *)docType error:(NSError **)outError { SLog(@"RDocument.writeToFile: %@ ofType: %@ ", absoluteURL, docType); NSString *oldFileType = (initialContentsType)?:ftRSource; if([[[absoluteURL absoluteString] lowercaseString] hasSuffix:@".rtf"]) { SLog(@" - docType was changed to rtf due to file extension"); if(initialContentsType) [initialContentsType release], initialContentsType = nil; initialContentsType = [[NSString stringWithString:@"public.rtf"] retain]; } else if([[[absoluteURL absoluteString] lowercaseString] hasSuffix:@".rd"]) { SLog(@" - docType was changed to Rd due to file extension"); if(initialContentsType) [initialContentsType release], initialContentsType = nil; initialContentsType = [[NSString stringWithString:ftRdDoc] retain]; [self setFileType:ftRdDoc]; } else { if(initialContentsType) [initialContentsType release], initialContentsType = nil; initialContentsType = [[NSString stringWithString:ftRSource] retain]; [self setFileType:ftRSource]; } SLog(@" - used docType %@", (initialContentsType)?:ftRSource); fileTypeWasChangedWhileSaving = ([initialContentsType isEqualToString:oldFileType]) ? NO : YES; return [super writeToURL:absoluteURL ofType:(initialContentsType)?:ftRSource error:outError]; } - (void) loadInitialContents { if (!initialContents) { SLog(@"RDocument.loadInitialContents: empty contents, skipping"); return; } SLog(@"RDocument.loadInitialContents: loading"); NSEnumerator *e = [[self windowControllers] objectEnumerator]; RDocumentWinCtrl *wc = nil; while ((wc = (RDocumentWinCtrl*)[e nextObject])) { if ([initialContentsType hasSuffix:@".rtf"]) { SLog(@" - new RTF contents (%d bytes) for window controller %@", [initialContents length], wc); [wc replaceContentsWithRtf: initialContents]; } else { const unsigned char *ic = [initialContents bytes]; NSString *cs; SLog(@" - try to auto-detect file encoding"); documentEncoding = NSUTF8StringEncoding; if ([initialContents length] > 1 && ((ic[0] == 0xff && ic[1] == 0xfe) || (ic[0] == 0xfe && ic[1] == 0xff))) // Unicode BOM documentEncoding = NSUnicodeStringEncoding; cs = [[NSString alloc] initWithData:initialContents encoding:documentEncoding]; if(!cs && [self fileURL]) { SLog(@" - failed to load as %d encoding, try to autodetect via initWithContentsOfURL:documentEncoding:error:", documentEncoding); cs = [[NSString alloc] initWithContentsOfURL:[self fileURL] usedEncoding:&documentEncoding error:nil]; } if (!cs) { // fall back to Latin1 since it's widely used SLog(@" - failed to load as %d encoding, falling back to Latin1", documentEncoding); documentEncoding = NSISOLatin1StringEncoding; cs = [[NSString alloc] initWithData:initialContents encoding:documentEncoding]; } if (!cs) { // fall back to MacRoman - old default SLog(@" - failed to load as %d encoding, falling back to MacRoman", documentEncoding); documentEncoding = NSMacOSRomanStringEncoding; cs = [[NSString alloc] initWithData:initialContents encoding:documentEncoding]; } if (cs) { SLog(@" - new string contents (%d chars) for window controller %@", [cs length], wc); // Important! otherwise the save box won't know [wc setFileEncoding:documentEncoding]; if(![initialContentsType isEqualToString:ftRSource] && ![initialContentsType isEqualToString:ftRdDoc]) { SLog(@" - set plain text mode"); [wc setPlain:YES]; } [wc replaceContentsWithString:cs]; } [cs release]; } } // release initialContents to clean heap esp. for large files if(initialContents) [initialContents release], initialContents=nil; } - (BOOL)revertToContentsOfURL:(NSURL *)absoluteURL ofType:(NSString *)typeName error:(NSError **)outError { SLog(@"RDocument:revertToContentsOfURL %@ of type %@", absoluteURL, typeName); if([typeName hasSuffix:@".rtf"]) { NSData *cs = [[NSData alloc] initWithContentsOfURL:absoluteURL]; if(cs) { [myWinCtrl replaceContentsWithRtf:cs]; [cs release]; // Remain focus [[myWinCtrl window] makeKeyWindow]; // Clear edited status [self updateChangeCount:NSChangeCleared]; return YES; } } else { NSString *cs = [[NSString alloc] initWithContentsOfURL:absoluteURL encoding:documentEncoding error:nil]; if(cs) { [myWinCtrl replaceContentsWithString:cs]; [cs release]; // Remain focus [[myWinCtrl window] makeKeyWindow]; // Clear edited status [self updateChangeCount:NSChangeCleared]; return YES; } } SLog(@" - couldn't revert document"); NSBeginAlertSheet(NLS(@"Reverting Document"), NLS(@"OK"), nil, nil, [myWinCtrl window], self, @selector(sheetDidEnd:returnCode:contextInfo:), nil, nil, NLS(@"Couldn't revert to saved document")); outError = nil; return YES; } - (void) reinterpretInEncoding: (NSStringEncoding) encoding { SLog(@"RDocument:reinterpretInEncoding - new encoding: %ld", encoding); NSString *sc = [myWinCtrl contentsAsString]; NSData *data = [sc dataUsingEncoding:documentEncoding allowLossyConversion:YES]; if(!data) { NSBeginAlertSheet(NLS(@"Convertion Error"), NLS(@"OK"), nil, nil, [myWinCtrl window], self, @selector(sheetDidEnd:returnCode:contextInfo:), nil, nil, [NSString stringWithFormat:@"%@ %@", NLS(@"Couldn't reinterpret the text by using the encoding"), [NSString localizedNameOfStringEncoding:encoding]]); SLog(@"- can't get data"); return; } NSString *ns = [[NSString alloc] initWithData:data encoding:encoding]; if (!ns) { [ns release]; NSBeginAlertSheet(NLS(@"Convertion Error"), NLS(@"OK"), nil, nil, [myWinCtrl window], self, @selector(sheetDidEnd:returnCode:contextInfo:), nil, nil, [NSString stringWithFormat:@"%@ %@", NLS(@"Couldn't reinterpret the text by using the encoding"), [NSString localizedNameOfStringEncoding:encoding]]); SLog(@" - can't create string"); return; } // Check for any non-valid (surrogate issues esp. if UTF16 is chosen) if ([ns UTF8String] == NULL) { [ns release]; NSBeginAlertSheet(NLS(@"Convertion Error"), NLS(@"OK"), nil, nil, [myWinCtrl window], self, @selector(sheetDidEnd:returnCode:contextInfo:), nil, nil, [NSString stringWithFormat:@"%@ %@", NLS(@"Couldn't reinterpret the text by using the encoding"), [NSString localizedNameOfStringEncoding:encoding]]); SLog(@" - string contains invalid bytes for chosen encoding"); return; } documentEncoding = encoding; [myWinCtrl setFileEncoding:documentEncoding]; // replace text in such a way that the user can perform undo: [[myWinCtrl textView] selectAll:nil]; [[myWinCtrl textView] insertText:ns]; [ns release]; } - (NSData *)dataRepresentationOfType:(NSString *)aType { if(encodingPopUp) { [[NSUserDefaults standardUserDefaults] setInteger:[[[encodingPopUp selectedItem] representedObject] unsignedIntegerValue] forKey:lastUsedFileEncoding]; documentEncoding = (NSStringEncoding)[[[encodingPopUp selectedItem] representedObject] unsignedIntegerValue]; } encodingPopUp = nil; // Insert code here to write your document from the given data. You can also choose to override -fileWrapperRepresentationOfType: or -writeToFile:ofType: instead. NSEnumerator *e = [[self windowControllers] objectEnumerator]; RDocumentWinCtrl *wc = nil; while ((wc = (RDocumentWinCtrl*)[e nextObject])) { if([aType hasSuffix:@".rtf"]) return [wc contentsAsRtf]; else return [[wc contentsAsString] dataUsingEncoding: documentEncoding]; } return nil; } /* This method is implemented to allow image data file to be loaded into R using open or drag and drop. In case of a successfull loading of image file, we don't want to create the UI for the document. */ - (BOOL)readFromFile:(NSString *)fileName ofType:(NSString *)docType{ if( [docType isEqual:@"R Data File"] || [[RController sharedController] isImageData:fileName] == 0){ [[RController sharedController] sendInput: [NSString stringWithFormat:@"load(\"%@\")",fileName]]; // [[NSDocumentController sharedDocumentController] setShouldCreateUI:NO]; return(YES); } else { // [[NSDocumentController sharedDocumentController] setShouldCreateUI:YES]; return( [super readFromFile: fileName ofType: docType] ); } } - (BOOL) loadDataRepresentation: (NSData *)data ofType:(NSString *)aType{ if (initialContents) [initialContents release], initialContents=nil; if (initialContentsType) [initialContentsType release], initialContentsType = nil; initialContentsType = [[NSString alloc] initWithString:aType]; initialContents = [data retain]; SLog(@"RDocument.loadDataRepresentation loading %d bytes of data and docType %@", [data length], initialContentsType); return YES; } + (void) changeDocumentTitle: (NSDocument *)document Title:(NSString *)title{ NSEnumerator *e = [[document windowControllers] objectEnumerator]; NSWindowController *wc = nil; while ((wc = [e nextObject])) { NSWindow *dw = [wc window]; [dw setTitle: title]; } } - (void) setEditable: (BOOL) editable { isEditable=editable; NSEnumerator *e = [[self windowControllers] objectEnumerator]; RDocumentWinCtrl *wc = nil; while ((wc = (RDocumentWinCtrl*)[e nextObject])) [wc setEditable: editable]; } - (BOOL) editable { return isEditable; } - (void) setREditFlag: (BOOL) flag { isREdit=flag; } - (BOOL) hasREditFlag { return isREdit; } - (NSString *)displayName { if(isREdit) return NLS(@"Object Editor"); return [super displayName]; } - (BOOL) isRTF { return (([self fileName] && [[[self fileName] lowercaseString] hasSuffix:@".rtf"]) || ([self fileType] && [[self fileType] hasSuffix:@".rtf"])); } - (BOOL) checkRdDocumentWithFilePath:(NSString*)inputFile reportSuccess:(BOOL)reportSuccess { [myWinCtrl setStatusLineText:[NSString stringWithFormat:@"%@ (%@)", NLS(@"Check Rd document…"), NLS(@"press ⌘. to cancel")]]; NSError *error = nil; [[[myWinCtrl textView] string] writeToFile:inputFile atomically:YES encoding:NSUTF8StringEncoding error:&error]; if(error != nil){ NSBeep(); NSLog(@"RDocument.checkRdDocument couldn't save a temporary file"); [myWinCtrl setStatusLineText:@""]; return NO; } NSString *convCmd = [NSString stringWithFormat:@"R --vanilla -q --slave --encoding=UTF-8 -e 'tools:::checkRd(\"%@\")'", inputFile]; NSError *bashError = nil; NSString *errMessage = [convCmd evaluateAsBashCommandAndError:&bashError]; if(bashError != nil) { if([bashError code] == 1) { errMessage = [NSString stringWithFormat:@"%@%@%@", errMessage, ([errMessage length])?@"\n":@"", [[bashError userInfo] objectForKey:NSLocalizedDescriptionKey]]; } else { NSBeep(); NSLog(@"RDocument.checkRdDocument bailed due to BASH error:\n%@", bashError); [myWinCtrl setStatusLineText:@""]; return NO; } } NSInteger errorMessageMaxLength = 900; if(![errMessage length]) errMessage = (reportSuccess) ? NLS(@"Check was successful.") : @""; else { errMessage = [errMessage stringByReplacingOccurrencesOfString:inputFile withString:NLS(@"Rd file")]; errMessage = [errMessage stringByReplacingOccurrencesOfString:[inputFile lastPathComponent] withString:NLS(@"Rd file")]; } if(![errMessage length]) { [myWinCtrl setStatusLineText:@""]; return YES; } if([errMessage length] > errorMessageMaxLength) errMessage = [[errMessage substringWithRange:NSMakeRange(0,errorMessageMaxLength)] stringByAppendingString:@"\n…"]; NSArray *errorLines = [errMessage componentsMatchedByRegex:[NSString stringWithFormat:@"%@:\\s*\\(?(\\d+)(\\)|-)?\\s*:?", NLS(@"Rd file")] capture:1L]; // Find first error line number since checkRd messages are appended and in // most cases more precise NSInteger errorLineNumber = -1; if([errorLines count]) { NSInteger i; NSInteger firstErrorLine = 10000000; NSInteger anErrorLine; for(i=0; i<[errorLines count]; i++) { anErrorLine = [(NSString*)[errorLines objectAtIndex:i] integerValue]; if(anErrorLine > 0 && (anErrorLine < firstErrorLine)) firstErrorLine = anErrorLine; } errorLineNumber = firstErrorLine; } [myWinCtrl setStatusLineText:@""]; NSAlert *alert = [NSAlert alertWithMessageText:NLS(@"Rd Check") defaultButton:NLS(@"OK") alternateButton:nil otherButton:nil informativeTextWithFormat:errMessage]; [alert setAlertStyle:NSWarningAlertStyle]; [alert runModal]; [[myWinCtrl window] makeKeyAndOrderFront:self]; [[myWinCtrl window] makeFirstResponder:[myWinCtrl textView]]; if(errorLineNumber >=0) { NSRange currentLineRange = NSMakeRange(0, 0); NSString *s = [[[myWinCtrl textView] textStorage] string]; NSInteger lineCounter = 0; while(lineCounter++ < errorLineNumber) currentLineRange = [s lineRangeForRange:NSMakeRange(NSMaxRange(currentLineRange), 0)]; SLog(@" - go to error line number %d", errorLineNumber); // select found line [[myWinCtrl textView] setSelectedRange:currentLineRange]; // scroll to found line [[myWinCtrl textView] centerSelectionInVisibleArea:nil]; // remove selection after 500ms [[myWinCtrl textView] performSelector:@selector(moveLeft:) withObject:nil afterDelay:0.5]; } return NO; } - (BOOL) checkRdDocument { if(rdToolsAreWorking) return NO; rdToolsAreWorking = YES; NSString *tempName = [NSTemporaryDirectory() stringByAppendingPathComponent: [NSString stringWithFormat: @"%.0f.", [NSDate timeIntervalSinceReferenceDate] * 1000.0]]; NSString *inputFile = [NSString stringWithFormat: @"%@%@", tempName, @"rd"]; BOOL success = [self checkRdDocumentWithFilePath:inputFile reportSuccess:YES]; [[NSFileManager defaultManager] removeItemAtPath:inputFile error:NULL]; rdToolsAreWorking = NO; return success; } - (void) insertRdDataTemplate { if(rdToolsAreWorking) return; rdToolsAreWorking = YES; [myWinCtrl setStatusLineText:[NSString stringWithFormat:@"%@", NLS(@"press ⌘. to cancel")]]; NSError *bashError = nil; NSString *templateStr = [@"R --vanilla --slave -e 'cat(unlist(prompt(Formaldehyde,NA)), sep=\"§\")'" evaluateAsBashCommandAndError:&bashError]; [myWinCtrl setStatusLineText:@""]; if(bashError != nil) { NSBeep(); NSLog(@"RDocumentWinCtrl.insertRdDataTemplate bailed due to BASH error:\n%@", bashError); rdToolsAreWorking = NO; return; } if(!templateStr) { NSBeep(); NSLog(@"RDocumentWinCtrl.insertRdDataTemplate bailed; no response from called R session"); rdToolsAreWorking = NO; return; } templateStr = [templateStr stringByReplacingOccurrencesOfString:@"§" withString:@"\n"]; templateStr = [templateStr stringByReplacingOccurrencesOfString:@"Formaldehyde" withString:NLS(@"DATA_NAME")]; templateStr = [templateStr stringByReplacingOccurrencesOfString:@"carb" withString:NLS(@"VAR_NAME_1")]; templateStr = [templateStr stringByReplacingOccurrencesOfString:@"optden" withString:NLS(@"VAR_NAME_2")]; [[myWinCtrl textView] insertText:templateStr]; NSRange newFunRange = [templateStr rangeOfString:NLS(@"DATA_NAME")]; [[myWinCtrl textView] setSelectedRange:newFunRange]; [[myWinCtrl textView] scrollRangeToVisible:newFunRange]; rdToolsAreWorking = NO; } - (void) insertRdFunctionTemplate { if(rdToolsAreWorking) return; rdToolsAreWorking = YES; [myWinCtrl setStatusLineText:[NSString stringWithFormat:@"%@", NLS(@"press ⌘. to cancel")]]; NSError *bashError = nil; NSString *templateStr = [@"R --vanilla --slave -e 'cat(unlist(prompt(mean.POSIXct,NA)), sep=\"§\")'" evaluateAsBashCommandAndError:&bashError]; [myWinCtrl setStatusLineText:@""]; if(bashError != nil) { NSBeep(); NSLog(@"RDocumentWinCtrl.insertRdFunctionTemplate bailed due to BASH error:\n%@", bashError); rdToolsAreWorking = NO; return; } if(!templateStr) { NSBeep(); NSLog(@"RDocumentWinCtrl.insertRdFunctionTemplate bailed; no response from called R session"); rdToolsAreWorking = NO; return; } templateStr = [templateStr stringByReplacingOccurrencesOfString:@"§" withString:@"\n"]; templateStr = [templateStr stringByReplacingOccurrencesOfString:@"mean.POSIXct" withString:NLS(@"FUNCTION_NAME")]; templateStr = [templateStr stringByReplacingOccurrencesOfRegex:@"\\n\\.POSIXct[^\\n]+" withString:@""]; [[myWinCtrl textView] insertText:templateStr]; NSRange newFunRange = [templateStr rangeOfString:NLS(@"FUNCTION_NAME")]; [[myWinCtrl textView] setSelectedRange:newFunRange]; [[myWinCtrl textView] scrollRangeToVisible:newFunRange]; rdToolsAreWorking = NO; } - (BOOL) convertRd2PDF { if(rdToolsAreWorking) return NO; rdToolsAreWorking = YES; BOOL success = YES; NSError *bashError = nil; // Try to find the path to the default tex distribution NSString *texPath = @""; [myWinCtrl setStatusLineText:[NSString stringWithFormat:@"%@", NLS(@"press ⌘. to cancel")]]; NSString *aPath = [@"which tex" evaluateAsBashCommand]; [myWinCtrl setStatusLineText:@""]; if(aPath && [aPath length]) { ; } else { [myWinCtrl setStatusLineText:[NSString stringWithFormat:@"%@", NLS(@"press ⌘. to cancel")]]; aPath = [@"eval `/usr/libexec/path_helper -s` && dirname `which tex`" evaluateAsBashCommand]; [myWinCtrl setStatusLineText:@""]; if(aPath && [aPath length]) { texPath = [NSString stringWithFormat:@"export PATH=$PATH:%@", aPath]; } } NSString *tempName = [NSTemporaryDirectory() stringByAppendingPathComponent: [NSString stringWithFormat: @"%.0f.", [NSDate timeIntervalSinceReferenceDate] * 1000.0]]; NSError *error; NSString *inputFile = [NSString stringWithFormat: @"%@%@", tempName, @"rd"]; NSString *pdfOutputFile = [NSString stringWithFormat: @"%@%@", tempName, @"pdf"]; NSString *errOutputFile = [NSString stringWithFormat: @"%@%@", tempName, @"txt"]; NSURL *pdfOutputFileURL = [NSURL URLWithString:pdfOutputFile]; [[[myWinCtrl textView] string] writeToFile:inputFile atomically:YES encoding:NSUTF8StringEncoding error:&error]; if(![self checkRdDocumentWithFilePath:inputFile reportSuccess:NO]) { [[NSFileManager defaultManager] removeItemAtPath:inputFile error:NULL]; [[NSFileManager defaultManager] removeItemAtPath:pdfOutputFile error:NULL]; [myWinCtrl setStatusLineText:@""]; rdToolsAreWorking = NO; return NO; } NSString *convCmd = [NSString stringWithFormat:@"#!/bin/sh\n%@\nR CMD Rd2pdf --no-preview --title='%@' --force --output='%@' '%@' 2>'%@'", texPath, [self displayName], pdfOutputFile, inputFile, errOutputFile]; bashError = nil; [myWinCtrl setStatusLineText:[NSString stringWithFormat:@"%@ (%@)", NLS(@"Rd → PDF…"), NLS(@"press ⌘. to cancel")]]; [convCmd runAsBashCommandAndError:&bashError]; [myWinCtrl setStatusLineText:@""]; NSFileManager *man = [NSFileManager defaultManager]; if(bashError == nil) { if([man fileExistsAtPath:pdfOutputFile]) [[HelpManager sharedController] showHelpFileForURL:pdfOutputFileURL]; } else { NSString *errMessage = [[bashError userInfo] objectForKey:NSLocalizedDescriptionKey]; if([man fileExistsAtPath:errOutputFile]) { bashError = nil; NSString *errMessages = [[[NSString alloc] initWithContentsOfFile:errOutputFile encoding:NSUTF8StringEncoding error:&bashError] autorelease]; if(bashError == nil && errMessages && [errMessages length]) errMessage = [errMessage stringByAppendingString:errMessages]; } NSAlert *alert = [NSAlert alertWithMessageText:NLS(@"Error") defaultButton:NLS(@"OK") alternateButton:nil otherButton:nil informativeTextWithFormat:[errMessage stringByReplacingOccurrencesOfString:inputFile withString:NLS(@"Rd file")]]; [alert setAlertStyle:NSWarningAlertStyle]; [alert runModal]; [[myWinCtrl window] makeKeyAndOrderFront:self]; [[myWinCtrl window] makeFirstResponder:[myWinCtrl textView]]; [[NSFileManager defaultManager] removeItemAtPath:inputFile error:NULL]; [[NSFileManager defaultManager] removeItemAtPath:pdfOutputFile error:NULL]; [[NSFileManager defaultManager] removeItemAtPath:errOutputFile error:NULL]; rdToolsAreWorking = NO; return NO; } rdToolsAreWorking = NO; // After 100 secs all temporary files will be deleted even if R was quitted meanwhile [self performSelector:@selector(removeFiles:) withObject:[NSArray arrayWithObjects:inputFile, pdfOutputFile, errOutputFile, nil] afterDelay:100]; return success; } - (BOOL) convertRd2HTML { if(rdToolsAreWorking) return NO; rdToolsAreWorking = YES; BOOL success = YES; NSString *tempName = [NSTemporaryDirectory() stringByAppendingPathComponent: [NSString stringWithFormat: @"%.0f.", [NSDate timeIntervalSinceReferenceDate] * 1000.0]]; NSString *RhomeCSS = @"R.css"; NSString *Rhome = [[RController sharedController] home]; if(!Rhome || ![Rhome length]) { [myWinCtrl setStatusLineText:[NSString stringWithFormat:@"%@", NLS(@"press ⌘. to cancel")]]; Rhome = [@"R --slave --vanilla -e 'cat(R.home())'" evaluateAsBashCommand]; [myWinCtrl setStatusLineText:@""]; } if(Rhome && [Rhome length]) { RhomeCSS = [NSString stringWithFormat:@"file://%@/doc/html/R.css", Rhome]; } NSError *error; NSString *inputFile = [NSString stringWithFormat: @"%@%@", tempName, @"rd"]; NSString *htmlOutputFile = [NSString stringWithFormat: @"%@%@", tempName, @"html"]; NSURL *htmlOutputFileURL = [NSURL URLWithString:htmlOutputFile]; [[[myWinCtrl textView] string] writeToFile:inputFile atomically:YES encoding:NSUTF8StringEncoding error:&error]; if(![self checkRdDocumentWithFilePath:inputFile reportSuccess:NO]) { [[NSFileManager defaultManager] removeItemAtPath:inputFile error:NULL]; rdToolsAreWorking = NO; return NO; } error = nil; [myWinCtrl setStatusLineText:[NSString stringWithFormat:@"%@ (%@)", NLS(@"Rd → HTML…"), NLS(@"press ⌘. to cancel")]]; NSString *convCmd = [NSString stringWithFormat:@"R CMD Rdconv -t html '%@' 2>/dev/null | perl -pe 's!R.css!%@!'> '%@'", inputFile, RhomeCSS, htmlOutputFile]; [convCmd evaluateAsBashCommandAndError:&error]; [myWinCtrl setStatusLineText:@""]; // Try to check if htmlOutputFile has content; if not don't come up with an empty window BOOL fsizeChecked = NO; #if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 UInt32 fsize = 0; NSFileManager *man = [[NSFileManager alloc] init]; NSDictionary *attrs = [man attributesOfItemAtPath:htmlOutputFile error:nil]; if(attrs) { fsize = [attrs fileSize]; fsizeChecked = YES; } [man release]; #endif if(!fsizeChecked || (fsizeChecked && fsize > 0)) { if(error == nil && [[NSFileManager defaultManager] fileExistsAtPath:htmlOutputFile]) [[HelpManager sharedController] showHelpFileForURL:htmlOutputFileURL]; else { NSBeep(); success = NO; } } rdToolsAreWorking = NO; // After 100 secs all temporary files will be deleted even if R was quitted meanwhile [self performSelector:@selector(removeFiles:) withObject:[NSArray arrayWithObjects:inputFile, htmlOutputFile, nil] afterDelay:100]; return success; } - (void)sheetDidEnd:(id)sheet returnCode:(int)returnCode contextInfo:(NSString*)contextInfo { // Order out the sheet - could be a NSPanel or NSWindow if ([sheet respondsToSelector:@selector(orderOut:)]) { [sheet orderOut:nil]; } else if ([sheet respondsToSelector:@selector(window)]) { [[sheet window] orderOut:nil]; } // Set the input focus to the last doc after closing a sheet [[(RDocumentController*)[NSDocumentController sharedDocumentController] findLastWindowForDocType:ftRSource] makeKeyAndOrderFront:nil]; } - (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem { NSString *iid = [toolbarItem itemIdentifier]; if ([iid isEqualToString: RETI_Save ] || [iid isEqualToString: RDETI_Save ]) return [self isDocumentEdited]; return YES; } - (void) removeFiles:(NSArray*)files { if(files && [files count]) { NSInteger i = 0; for(i=0; i<[files count]; i++) [[NSFileManager defaultManager] removeItemAtPath:[files objectAtIndex:i] error:NULL]; } } @end