The R Project SVN R-packages

Rev

Rev 5588 | Rev 5627 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

/*
 *  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.
 *
 *  $Id: RController.m 5589 2010-05-31 14:25:02Z urbaneks $
 */

#import "RGUI.h"
#include <R.h>
#include <Rversion.h>
#include <Rinternals.h>
#include <R_ext/Parse.h>
#include <Fileio.h>
#if (R_VERSION >= R_Version(2,1,0))
// Rinterface is public, but present only in 2.1+
#include <Rinterface.h>
#else
// if there is no Rinterface, we need private Defn.h
#include "privateR.h"
#endif
#include <langinfo.h>
#include <locale.h>

#if R_VERSION >= R_Version(2,7,0)
#include <R_ext/QuartzDevice.h>
#endif

#import <sys/fcntl.h>
#import <sys/select.h>
#import <sys/types.h>
#import <sys/time.h>
#import <sys/wait.h>
#import <signal.h>
#import <unistd.h>
#import "RController.h"
#import "REngine/Rcallbacks.h"
#import "REngine/REngine.h"
#import "RConsoleTextStorage.h"
#import "RDocumentWinCtrl.h"
#import "Quartz/QuartzDevice.h"

#import "Tools/Authorization.h"

#import "Preferences.h"
#import "SearchTable.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

// size of the console output cache buffer
#define DEFAULT_WRITE_BUFFER_SIZE 32768
// high water-mark of the buffer - it's [length - x] where x is the smallest possible size to be flushed before a new string will be split.
#define writeBufferHighWaterMark  (DEFAULT_WRITE_BUFFER_SIZE-4096)
// low water-mark of the buffer - if less than the water mark is available then the buffer will be flushed
#define writeBufferLowWaterMark   2048

/*  RController.m: main GUI code originally based on Simon Urbanek's work of embedding R in Cocoa app (RGui?)
The Code and File Completion is due to Simon U.
History handler is due to Simon U.
*/

typedef struct {
    ParseStatus    status;
    int            prompt_type;
    int            browselevel;
    unsigned char  buf[1025];
    unsigned char *bufp;
} R_ReplState;

extern R_ReplState state;

void run_Rmainloop(void); // from Rinit.c
extern void RGUI_ReplConsole(SEXP rho, int savestack, int browselevel); // from Rinit.c
extern int RGUI_ReplIteration(SEXP rho, int savestack, int browselevel, R_ReplState *state);

// from Defn.h

int R_SetOptionWidth(int);

#import "RController.h"
#import "Tools/CodeCompletion.h"
#import "Tools/FileCompletion.h"
#import "HelpManager.h"
#import "RDocument.h"
#import "PackageManager.h"
#import "DataManager.h"
#import "PackageInstaller.h"
#import "WSBrowser.h"
#import "HelpManager.h"
#import "Quartz/RQuartz.h"
#import "RDocumentController.h"
#import "SelectList.h"
#import "VignettesController.h"

#import <unistd.h>
#import <sys/fcntl.h>

static RController* sharedRController;

@interface NSApplication (ScriptingSupport)
- (id)handleDCMDCommand:(NSScriptCommand*)command;
@end

@implementation NSApplication (ScriptingSupport)
- (id)handleDCMDCommand:(NSScriptCommand*)command
{
//  if (![[[RController sharedController] getRConsoleWindow] isKeyWindow]) {
//      [[[RController sharedController] getRConsoleWindow] makeKeyWindow];
//      SLog(@" RConsole set to key window");
//  }
    NSDictionary *args = [command evaluatedArguments];
    NSString *cmd = [args objectForKey:@""];
    if (!cmd || [cmd isEqualToString:@""])
        return [NSNumber numberWithBool:NO];
    [[RController sharedController] sendInput: cmd];
    /* post an event to wake the event loop in order to process the command */
    int wn = [[[RController sharedController] getRConsoleWindow] windowNumber];
//  SLog(@"Key window number %d", wn);
    [NSApp postEvent:[NSEvent otherEventWithType: NSApplicationDefined 
                                        location: (NSPoint){0,0} modifierFlags: 0 timestamp: 0
                                    windowNumber: wn context: NULL subtype: 0 data1: 0 data2: 0
        ] atStart: YES];
    return [NSNumber numberWithBool:YES];
}
@end

@implementation RController

- (id) init {
    self = [super init];
    
    runSystemAsRoot = NO;
    toolbar = nil;
    toolbarStopItem = nil;
    rootFD = -1;
    childPID = 0;
    RLtimer = nil;
    lastShownWD = nil;
    busyRFlag = YES;
    appLaunched = NO;
    terminating = NO;
    processingEvents = NO;
    breakPending = NO;
    outputPosition = promptPosition = committedLength = 0;
    consoleInputQueue = [[NSMutableArray alloc] initWithCapacity:8];
    currentConsoleInput = nil;
    forceStdFlush = NO;
    writeBufferLen = DEFAULT_WRITE_BUFFER_SIZE;
    writeBufferPos = writeBuffer = (char*) malloc(writeBufferLen);
    readConsTransBufferSize = 1024; // initial size - will grow as needed
    readConsTransBuffer = (char*) malloc(readConsTransBufferSize);
    textViewSync = [[NSString alloc] initWithString:@"consoleTextViewSemahphore"];
    
    consoleColorsKeys = [[NSArray alloc] initWithObjects:
        backgColorKey, inputColorKey, outputColorKey, promptColorKey,
        stderrColorKey, stdoutColorKey, rootColorKey, nil];
    defaultConsoleColors = [[NSArray alloc] initWithObjects: // default colors
        [NSColor whiteColor], [NSColor blueColor], [NSColor blackColor], [NSColor purpleColor],
        [NSColor redColor], [NSColor grayColor], [NSColor purpleColor], nil];
    consoleColors = [defaultConsoleColors mutableCopy];
    
    currentFontSize = [Preferences floatForKey: FontSizeKey withDefault: 11.0];
    textFont = [[NSFont userFixedPitchFontOfSize:currentFontSize] retain];
        
    specialCharacters = [[NSCharacterSet characterSetWithCharactersInString:@"\r\b\a"] retain];
    
    return self;
}

- (void) setRootFlag: (BOOL) flag
{
    if (!flag) removeRootAuthorization();
    runSystemAsRoot=flag;
    
    {
        NSArray * ia = [toolbar items];
        int l = [ia count], i=0;
        while (i<l) {
            NSToolbarItem *ti = [ia objectAtIndex:i];
            if ([[ti itemIdentifier] isEqual:AuthenticationToolbarItemIdentifier]) {
                [ti setImage: [NSImage imageNamed: flag?@"lock-unlocked":@"lock-locked"]];
                break;
            }
            i++;
        }
    }
}

- (BOOL) getRootFlag { return runSystemAsRoot; }

- (void) setRootFD: (int) fd {
    rootFD=fd;
}

- (NSFont*) currentFont
{
    return textFont;
}

/* before NSApplication is ready, we need to catch any odoc events that arrive, otherwise we lose them */
- (void)handleAppleEvent:(NSAppleEventDescriptor *)event 
          withReplyEvent:(NSAppleEventDescriptor *)replyEvent
{
    int docs = [event numberOfItems];
    int i = 1;
    SLog(@"RController.handleAppleEvent:%@ withReplyEvent:%@", event, replyEvent);
    while (i <= docs) {     
        NSAppleEventDescriptor *d = [event descriptorAtIndex:i];
        if (d) {
            CFURLRef url;
            d = [d coerceToDescriptorType:typeFSRef];
            url = CFURLCreateFromFSRef(kCFAllocatorDefault, [[d data] bytes]);
            if (url) {
                NSString *pathName = (NSString *)CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle);
                [pathName autorelease];
                if (!appLaunched)
                    [pendingDocsToOpen addObject: pathName];
                else
                    [self application:NSApp openFile:pathName];
                CFRelease(url);
            }
        }
        i++;
    }
}


- (void) awakeFromNib {
    char *args[5]={ "R", "--no-save", "--no-restore-data", "--gui=cocoa", 0 };
    SLog(@"RController.awakeFromNib");
    
    requestSaveAction = nil;
    
    sharedRController = self;
    pendingDocsToOpen = [[NSMutableArray alloc] init];
    [[NSAppleEventManager sharedAppleEventManager] setEventHandler:self andSelector:@selector(handleAppleEvent:withReplyEvent:) forEventClass:kCoreEventClass andEventID:kAEOpenDocuments];
    [consoleTextView setConsoleMode: YES];
    NSLayoutManager *lm = [[consoleTextView layoutManager] retain];
    NSTextStorage *origTS = [[consoleTextView textStorage] retain];
    RConsoleTextStorage * textStorage = [[RConsoleTextStorage alloc] init];
    [origTS removeLayoutManager:lm];
    [textStorage addLayoutManager:lm];
    [lm release];
    [origTS release];
    
    RTextView_autoCloseBrackets = [Preferences flagForKey:kAutoCloseBrackets withDefault:YES];
    
    [RConsoleWindow setBackgroundColor:[defaultConsoleColors objectAtIndex:iBackgroundColor]]; // we need this, because "update" doesn't touch the color if it's equal - and by default the window has *no* background - not even the default one, so we bring it in sync
    [RConsoleWindow setOpaque:NO]; // Needed so we can see through it when we have clear stuff on top
    [consoleTextView setDrawsBackground:NO];
    [[consoleTextView enclosingScrollView] setDrawsBackground:NO];
    
    SLog(@" - load preferences");
    [self updatePreferences]; // first update, then add self
    [[Preferences sharedPreferences] addDependent: self];
    
    SLog(@" - init R_LIBS");    
    { // first initialize R_LIBS if necessary
        NSString *prefStr = [Preferences stringForKey:miscRAquaLibPathKey withDefault:nil];
        BOOL flag = !isAdmin(); // the default is YES for users and NO for admins
        if (prefStr)
            flag=[prefStr isEqualToString: @"YES"];
        if (flag) {
            char *cRLIBS = getenv("R_LIBS");
            NSString *addPath = [[NSString stringWithFormat:@"~/Library/R/%@/library", Rapp_R_version_short] stringByExpandingTildeInPath];
            if (![[NSFileManager defaultManager] fileExistsAtPath:addPath]) { // make sure the directory exists
                [[NSFileManager defaultManager] createDirectoryAtPath:[@"~/Library/R" stringByExpandingTildeInPath] attributes:nil];
                [[NSFileManager defaultManager] createDirectoryAtPath:[[NSString stringWithFormat:@"~/Library/R/%@", Rapp_R_version_short] stringByExpandingTildeInPath] attributes:nil];
                [[NSFileManager defaultManager] createDirectoryAtPath:addPath attributes:nil];
            }
            if (cRLIBS && *cRLIBS)
                addPath = [NSString stringWithFormat: @"%s:%@", cRLIBS, addPath];
            setenv("R_LIBS", [addPath UTF8String], 1);
            SLog(@" - setting R_LIBS=%s", [addPath UTF8String]);
        }
    }
    SLog(@" - set APP VERSION (%s) and REVISION (%@)", R_GUI_VERSION_STR,
         [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]);
    setenv("R_GUI_APP_VERSION", R_GUI_VERSION_STR, 1);
    setenv("R_GUI_APP_REVISION", [(NSString*)[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"] UTF8String], 1);
    
    SLog(@" - set R home");
    if (!getenv("R_HOME")) {
        NSBundle *rfb = [NSBundle bundleWithIdentifier:@"org.r-project.R-framework"];
        if (!rfb) {
            SLog(@" * problem: R_HOME is not set and I can't find the framework bundle");
            if ([[NSFileManager defaultManager] fileExistsAtPath:@"/Library/Frameworks/R.framework/Resources/bin/R"]) {
                SLog(@" * I'm being desperate and I found R at /Library/Frameworks/R.framework - so I'll use it, wish me luck");
                setenv("R_HOME", "/Library/Frameworks/R.framework/Resources", 1);
            } else
                SLog(@" * I didn't even find R framework in the default location, I'm giving up - you're on your own");
        } else {
            SLog(@"   %s", [[rfb resourcePath] UTF8String]);
            setenv("R_HOME", [[rfb resourcePath] UTF8String], 1);
        }
    }
    if (getenv("R_HOME"))
        home = [[NSString alloc] initWithUTF8String:getenv("R_HOME")];
    else
        home = [[NSString alloc] initWithString:@""];

#if (R_VERSION >= R_Version(2,2,0))
    /* 2.2.0 is now alpha, so it should be ok by now - keep this for 2.3 in case wee need it
    if (!getenv("RGUI_NOWARN_RDEVEL"))
        NSRunAlertPanel(@"Using R-devel (unstable!)",@"You are using R-devel (to become 2.2.0)\n\nSeveral properites that I rely on are changing in R-devel. I will take a wild guess as of how they change, but that guess can be completely wrong. Most importantly you have to use the default build settings. Proceed at your own risk.\n\n(Set RGUI_NOWARN_RDEVEL to get rid of this message)",NLS(@"OK"),nil,nil);
     */
    {
        char tp[1024];
        /* since 2.2.0 those are set in the R shell script, so we need to set them as well */
        /* FIXME: possible buffer-overflow attack by over-long R_HOME */
        if (!getenv("R_INCLUDE_DIR")) {
            strcpy(tp, getenv("R_HOME")); strcat(tp, "/include"); setenv("R_INCLUDE_DIR", tp, 1);
        }
        if (!getenv("R_SHARE_DIR")) {
            strcpy(tp, getenv("R_HOME")); strcat(tp, "/share"); setenv("R_SHARE_DIR", tp, 1);
        }
        if (!getenv("R_DOC_DIR")) {
            strcpy(tp, getenv("R_HOME")); strcat(tp, "/doc"); setenv("R_DOC_DIR", tp, 1);
        }
    }

#if (R_VERSION >= R_Version(2,3,0))
    /* in 2.3.0 we have to set R_ARCH if appropriate */
#ifdef __ppc__
#define arch_lib_nss @"/lib/ppc"
#define arch_str "/ppc"
#else
#ifdef __ppc64__
#define arch_lib_nss @"/lib/ppc64"
#define arch_str "/ppc64"
#else
#ifdef __i386__
#define arch_lib_nss @"/lib/i386"
#define arch_str "/i386"
#else
#ifdef __x86_64__
#define arch_lib_nss @"/lib/x86_64"
#define arch_str "/x86_64"
#endif
#endif
#endif
#endif
#ifdef arch_lib_nss
    if (!getenv("R_ARCH")) {
        if ([[NSFileManager defaultManager] fileExistsAtPath:[[NSString stringWithUTF8String:getenv("R_HOME")] stringByAppendingString: arch_lib_nss]])
            setenv("R_ARCH", arch_str, 1);
    }
#else
#warning "Unknown architecture, R_ARCH won't be set automatically."
#endif

#endif /* R 2.3.0 */
#endif /* R 2.2.0 */
    
    /* setup LANG variable to match the system locale based on user's CFLocale */
#if (R_VERSION >= R_Version(2,1,0))
    SLog(@" - set locale");
    if ([Preferences stringForKey:@"force.LANG"]) {
        const char *ls = [[Preferences stringForKey:@"force.LANG"] UTF8String];
        if (*ls) {
            setenv("LANG", ls, 1);
            SLog(@" - force.LANG present, setting LANG to \"%s\"", ls);
        } else
            SLog(@" - force.LANG present, but empty. LANG won't be set at all.");
    } else if ([Preferences flagForKey:@"ignore.system.locale"]==YES) {
        setenv("LANG", "en_US.UTF-8", 1);
        SLog(@" - ignore.system.locale is set to YES, using en_US.UTF-8");
    } else {
        char cloc[64];
        char *c = getenv("LANG");
        cloc[63]=0;
        if (c)
            strcpy(cloc, c);
        else {
            CFLocaleRef lr = CFLocaleCopyCurrent();
            CFStringRef ls = CFLocaleGetIdentifier(lr);
            *cloc=0;
            if (ls) {
                NSString *lss = (NSString*)ls;
                NSRange atr = [lss rangeOfString:@"@"];
                SLog(@"   CFLocaleGetIdentifier=\"%@\"", ls);
                if (atr.location != NSNotFound) {
                    lss = [lss substringToIndex:atr.location];
                    SLog(@"   - it contains @, stripped to \"%@\"", lss);
                }
                strncpy(cloc, [lss UTF8String], 63);
            }
            if (! *cloc) {
                SLog(@"   CFLocaleGetIdentifier is empty, falling back to en_US.UTF-8");
                strcpy(cloc,"en_US.UTF-8");
            }
            if (lr) CFRelease(lr);
        }
            
        if (!strchr(cloc,'.'))
            strcat(cloc,".UTF-8");
        setenv("LANG", cloc, 1);
        SLog(@" - setting LANG=%s", getenv("LANG"));
    }
#endif

    BOOL noReenter = [Preferences flagForKey:@"REngine prevent reentrance"];
    if (noReenter == YES) preventReentrance = YES;
    
    SLog(@" - init R");
    [[[REngine alloc] initWithHandler:self arguments:args] setCocoaHandler:self];

    /* set save action */
    [[REngine mainEngine] setSaveAction:[Preferences stringForKey:saveOnExitKey withDefault:@"ask"]];
    [[REngine mainEngine] disableRSignalHandlers:[Preferences flagForKey:@"Disable R signal handlers" withDefault:
#ifdef DEBUG_RGUI
        YES
#else
        NO
#endif
        ]];

    SLog(@" - font and other widgets");
    [consoleTextView setFont:textFont];
    [fontSizeStepper setIntValue:currentFontSize];
    [fontSizeField takeIntValueFrom:fontSizeStepper];
    {
        NSMutableDictionary *md = [[consoleTextView typingAttributes] mutableCopy];
        [md setObject: [consoleColors objectAtIndex:iInputColor] forKey: @"NSColor"];
        [consoleTextView setTypingAttributes:[NSDictionary dictionaryWithDictionary:md]];
        [md release];
    }
    [consoleTextView setContinuousSpellCheckingEnabled:NO]; // force 'no spell checker'
    [[consoleTextView textStorage] setDelegate:self];
        
    //  [consoleTextView changeColor: inputColor];
    [consoleTextView display];
    [self setupToolbar];

    [RConsoleWindow setDocumentEdited:YES];
    
    SLog(@" - setup timer");
    WDirtimer = [NSTimer scheduledTimerWithTimeInterval:0.5
                                                 target:self
                                               selector:@selector(showWorkingDir:)
                                               userInfo:0
                                                repeats:YES];
    
    hist=[[History alloc] init];
    
    
    BOOL WantThread = ([Preferences flagForKey:@"Redirect stdout/err"] != NO);
    
    SLog(@" - setup stdout/err grabber");
    if (WantThread){ // re-route the stdout to our own file descriptor and use ConnectionCache on it
        int pfd[2];
        pipe(pfd);
        dup2(pfd[1], STDOUT_FILENO);
        close(pfd[1]);
        
        stdoutFD=pfd[0];
        
        pipe(pfd);
#ifndef PLAIN_STDERR
        if ([Preferences flagForKey:@"Ignore stderr"] != YES) {
            dup2(pfd[1], STDERR_FILENO);
            close(pfd[1]);
        }
#endif
        
        stderrFD=pfd[0];
        
        [self addConnectionLog];
    }
    
    SLog(@" - set cwd and load history");
    [historyView setDoubleAction: @selector(historyDoubleClick:)];
    
    currentSize = [[consoleTextView textContainer] containerSize].width;
    //currentFontSize = [[consoleTextView font] pointSize];
    currentConsoleWidth = -1;
    [[NSFileManager defaultManager] changeCurrentDirectoryPath: [[Preferences stringForKey:initialWorkingDirectoryKey withDefault:@"~"] stringByExpandingTildeInPath]];
    if ([Preferences flagForKey:importOnStartupKey withDefault:YES]) {
        [self doLoadHistory:nil];
    }
        
    SLog(@" - awake is done");
}

- (NSString*) home
{
    return home;
}

-(void) applicationDidFinishLaunching: (NSNotification *)aNotification
{
    NSString *fname = nil;
    SLog(@"RController:applicationDidFinishLaunching");
    SLog(@" - clean up and flush console");
    [self setOptionWidth:YES];
    [consoleTextView setEditable:YES];
    [self flushROutput];
    
    RSEXP *xPT = [[REngine mainEngine] evaluateString:@".Platform$pkgType"];
    if (xPT) {
        NSString *pkgType = [xPT string];
        SLog(@" - pkgType in this R: \"%@\"", pkgType);
        if (pkgType) [[PackageInstaller sharedController] setPkgType:pkgType];
        [xPT release];
    }
    
#if R_VERSION >= R_Version(2,7,0)
    /* set embedding flags such that Quartz knows that we have set everything up already */
    QuartzFunctions_t *qf = getQuartzFunctions();
    if (qf) {
        int eflags = QP_Flags_CFLoop | QP_Flags_Cocoa | QP_Flags_Front;
        qf->SetParameter(NULL, QuartzParam_EmbeddingFlags, &eflags);
    }
#endif
    
    SLog(@" - setup notification and timers");
    [[NSNotificationCenter defaultCenter] 
        addObserver:self
           selector:@selector(RConsoleDidResize:)
               name:NSWindowDidResizeNotification
             object: RConsoleWindow];
    
    timer = [NSTimer scheduledTimerWithTimeInterval:0.05
                                             target:self
                                           selector:@selector(otherEventLoops:)
                                           userInfo:0
                                            repeats:YES];
    Flushtimer = [NSTimer scheduledTimerWithTimeInterval:0.5
                                                  target:self
                                                selector:@selector(flushTimerHook:)
                                                userInfo:0
                                                 repeats:YES];
    
    // once we're ready with the doc transition, the following will actually fire up the cconsole window
    //[[NSDocumentController sharedDocumentController] openUntitledDocumentOfType:@"Rcommand" display:YES];
    
    fname = [[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingString: @"/.RData"] stringByExpandingTildeInPath];
    if (([pendingDocsToOpen count] == 0) && 
        ([[NSFileManager defaultManager] fileExistsAtPath: fname])) {
        NSString *cmd;
        cmd = [[[NSString alloc] initWithString: @"load(\""] stringByAppendingString: fname];
        cmd = [cmd stringByAppendingString: @"\")"]; 
        [[REngine mainEngine] executeString:cmd];
        NSString *msg = [[[[NSString alloc] initWithString: NLS(@"[Workspace restored from ")] stringByAppendingString: fname] stringByAppendingString: @"]\n\n"];      
        [self handleWriteConsole: msg];
        SLog(@"RController.applicationDidFinishLaunching - load workspace %@", fname);
    }

    SLog(@"RController.applicationDidFinishLaunching - show main window");
    [RConsoleWindow makeKeyAndOrderFront:self];

    SLog(@"RController.openDocumentsPending: process pending 'odoc' events");
    if ([pendingDocsToOpen count]>0) {
        NSEnumerator *enumerator = [pendingDocsToOpen objectEnumerator];
        NSString *fileName;
        SLog(@" - %d documents to open", [pendingDocsToOpen count]);
        while (fileName = (NSString*) [enumerator nextObject])
            [self application:NSApp openFile:fileName];
        [pendingDocsToOpen removeAllObjects];
    }

    [[REngine mainEngine] executeString:@"if (exists('.First') && is.function(.First)) .First()"];

#if R_VERSION >= R_Version(2,7,0)
    SLog(@" - set Quartz preferences (if necessary)");
    BOOL flag=[Preferences flagForKey:useQuartzPrefPaneSettingsKey withDefault: NO];
    if (flag) {
        NSString *qWidth = [Preferences stringForKey:quartzPrefPaneWidthKey withDefault: @"5"];
        NSString *qHeight = [Preferences stringForKey:quartzPrefPaneHeightKey withDefault: @"5"];
        NSString *qDPI = [Preferences stringForKey:quartzPrefPaneDPIKey withDefault: @""];
        [[REngine mainEngine] executeString:[NSString stringWithFormat:@"quartz.options(width=%@,height=%@,dpi=%@)", qWidth, qHeight, ([qDPI length] == 0) ? @"NA_real_" : qDPI]];
    }
#endif
    
    appLaunched = YES;
    [self setStatusLineText:@""];

    {
    // check locale
    RSEXP * x = [[REngine mainEngine] evaluateString:@"Sys.getlocale()"];
    if (x) {
        NSString *s = [x string];
        if (s) {
        NSRange r = [s rangeOfString:@"utf" options:NSCaseInsensitiveSearch];
        if (r.location == NSNotFound) {
            [self writeConsoleDirectly:NLS(@"WARNING: You're using a non-UTF8 locale, therefore only ASCII characters will work.\nPlease read R for Mac OS X FAQ (see Help) section 9 and adjust your system preferences accordingly.\n") withColor:[NSColor redColor]];
        }
        }
        [x release];
    }
    }
    
    
    SLog(@" - done, ready to go");
}

- (BOOL)appLaunched {
    return appLaunched;
}

-(void) addConnectionLog
{
    NSPort *port1;
    NSPort *port2;
    NSArray *portArray;
    NSConnection *connectionToTransferServer;
                
    port1 = [NSPort port];
    port2 = [NSPort port];
    connectionToTransferServer = [[NSConnection alloc] initWithReceivePort:port1 sendPort:port2];
    [connectionToTransferServer setRootObject:self];
    
    portArray = [NSArray arrayWithObjects:port2, port1, nil];
    [NSThread detachNewThreadSelector:@selector(readThread:)
                             toTarget:self
                           withObject:portArray];
}

- (void) readThread: (NSArray *)portArray
{
    NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
    NSConnection *connectionToController;
    RController *rc = nil;
    unsigned int bufSize=2048;
    char *buf=(char*) malloc(bufSize+16);
    int n=0, pib=0, flushMark=bufSize-(bufSize>>2);
    int bufFD=0;
    fd_set readfds;
    struct timeval timv;
    BOOL truncated = NO;
    
    timv.tv_sec=0; timv.tv_usec=300000; /* timeout */
    
    connectionToController = [NSConnection connectionWithReceivePort:[portArray objectAtIndex:0]
                                                            sendPort:[portArray objectAtIndex:1]];
    
    rc = ((RController *)[connectionToController rootProxy]);

    // set timeouts only *after* the connection was established
    [connectionToController setRequestTimeout:2.0];
    [connectionToController setReplyTimeout:2.0];

    fcntl(stdoutFD, F_SETFL, O_NONBLOCK);
    fcntl(stderrFD, F_SETFL, O_NONBLOCK);
    while (1) {
        int selr=0, maxfd=stdoutFD;
        int validRootFD=-1;
        FD_ZERO(&readfds);
        FD_SET(stdoutFD,&readfds);
        FD_SET(stderrFD,&readfds); if(stderrFD>maxfd) maxfd=stderrFD;
        if (rootFD!=-1) {
            validRootFD = rootFD; // we copy it as to not run into threading problems when it is changed while we're in the middle of processing
            FD_SET(rootFD,&readfds);
            if(rootFD>maxfd) maxfd=rootFD;
        }
        selr=select(maxfd+1, &readfds, 0, 0, &timv);
        if (FD_ISSET(stdoutFD, &readfds)) {
            if (bufFD!=0 && pib>0) {
                @try{
                    [rc writeLogsWithBytes:buf length:pib type:bufFD];
                    if (truncated) { [rc writeLogsWithBytes:"\n(WARNING: partial output only, ask package author to use Rprintf instead!)\n" length:-1 type:1]; truncated=NO; }
                } @catch(NSException *ex) {
                    truncated = YES;
                }
                pib=0;
            }
            bufFD=0;
            while (pib<bufSize && (n=read(stdoutFD,buf+pib,bufSize-pib))>0)
                pib+=n;
            if (pib>flushMark) { // if we reach the flush mark, dump it
                @try{
                    [rc writeLogsWithBytes:buf length:pib type:bufFD];
                    if (truncated) { [rc writeLogsWithBytes:"\n(WARNING: partial output only, ask package author to use Rprintf instead!)\n" length:-1 type:1]; truncated=NO; }
                } @catch(NSException *ex) {
                    truncated = YES;
                }
                pib=0;
            }
        } 
        if (FD_ISSET(stderrFD, &readfds)) {
            if (bufFD!=1 && pib>0) {
                @try {
                    [rc writeLogsWithBytes:buf length:pib type:bufFD];
                } @catch(NSException *ex) {
                }               
                pib=0;
            }
            bufFD=1;
            while (pib<bufSize && (n=read(stderrFD,buf+pib,bufSize-pib))>0)
                pib+=n;
            if (pib>flushMark) { // if we reach the flush mark, dump it
                @try{
                    [rc writeLogsWithBytes:buf length:pib type:bufFD];
                } @catch(NSException *ex) {
                }
                pib=0;
            }
        }
        if (validRootFD!=-1 && FD_ISSET(validRootFD, &readfds)) {
            if (bufFD!=2 && pib>0) {
                @try{
                    [rc writeLogsWithBytes:buf length:pib type:bufFD];
                } @catch(NSException *ex) {
                    truncated = YES;
                }
                pib=0;
            }
            bufFD=2;
            while (pib<bufSize && (n=read(validRootFD,buf+pib,bufSize-pib))>0)
                pib+=n;
            if (n==0 || pib>flushMark) { // if we reach the flush mark, dump it
                @try{
                    [rc writeLogsWithBytes:buf length:pib type:bufFD];
                } @catch(NSException *ex) {
                    truncated = YES;
                }
                pib=0;
            }
            if (n==0) rootFD=-1; // we indicate EOF on the rootFD by setting it to -1
        }
        if ((forceStdFlush || selr==0) && pib>0) { // dump also if we got a timeout
            @try{
                [rc writeLogsWithBytes:buf length:pib type:bufFD];
            } @catch(NSException *ex) {
                truncated = YES;
            }
            pib=0;
        }
    }
    free(buf);
    
    [pool release];
}

- (void) flushStdConsole
{
    fflush(stderr);
    fflush(stdout);
    forceStdFlush=YES;
}

- (void) addChildProcess: (pid_t) pid
{
    childPID=pid;
    if (pid>0 && toolbarStopItem) [toolbarStopItem setEnabled:YES];
}

- (void) rmChildProcess: (pid_t) pid
{
    childPID=0;
    if (!busyRFlag && toolbarStopItem) [toolbarStopItem setEnabled:NO];
    [self flushStdConsole];
}

// When you have the toolbar in text-only mode, this action is called
// by the toolbar item's menu to make the font bigger.  We just change the stepper control and
// call our -changeFontSize: action.
-(IBAction) fontSizeBigger:(id)sender
{
    if ([RConsoleWindow isKeyWindow]) {
        [fontSizeStepper setIntValue:[fontSizeStepper intValue]+1];
        [self changeFontSize:NULL];
    } else
        [[NSFontManager sharedFontManager] modifyFont:sender];
}

// When you have the toolbar in text-only mode, this action is called
// by the toolbar item's menu to make the font smaller.  We just change the stepper control and
// call our -changeFontSize: action.
-(IBAction) fontSizeSmaller:(id)sender
{
    if ([RConsoleWindow isKeyWindow]) {
        [fontSizeStepper setIntValue:[fontSizeStepper intValue]-1];
        [self changeFontSize:NULL];
    } else
        [[NSFontManager sharedFontManager] modifyFont:sender];
    
}

// This action is called to change the font size.  It's called by the NSPopUpButton in the toolbar item's 
// custom view, and also by the above routines called from the toolbar item's menu (in text-only mode).
-(IBAction) changeFontSize:(id)sender
{
    int newFontSize = [fontSizeStepper intValue];   
    [fontSizeField takeIntValueFrom:fontSizeStepper];
    /*
    theFont=[consoleTextView font];
    theFont=[[NSFontManager sharedFontManager] convertFont:theFont toSize:[fontSizeStepper intValue]];
    [consoleTextView setFont:theFont];
    [self setOptionWidth:NO];
    [[NSUserDefaults standardUserDefaults] setFloat: [fontSizeStepper intValue] forKey:FontSizeKey];
     */
    [Preferences setKey:FontSizeKey withFloat:(float) newFontSize];
}

-(IBAction) clearConsole:(id)sender {
    if (promptPosition > 0) {
        committedLength -= promptPosition;
        outputPosition = 0;
        [consoleTextView replaceCharactersInRange: NSMakeRange(0,promptPosition) withString:@""];
        promptPosition = 0;
    }
}

extern BOOL isTimeToFinish;

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app {
    terminating = YES;

    if (![self windowShouldClose:self]) {
        terminating = NO;
        return NSTerminateCancel;
    }
    
    if(timer){
        [timer invalidate];
        timer = nil;
    }
    
    if(RLtimer){
        [RLtimer invalidate];
        RLtimer = nil;
    }
    
    if(Flushtimer){
        [Flushtimer invalidate];
        Flushtimer = nil;
    }
    
    if(WDirtimer){
        [WDirtimer invalidate];
        WDirtimer = nil;
    }
    
    return NSTerminateNow;
}


- (void) dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [[Preferences sharedPreferences] removeDependent:self];
    [defaultConsoleColors release];
    [consoleColors release];
    [consoleColorsKeys release];
    [super dealloc];
}

- (void) flushTimerHook: (NSTimer*) source
{
    [self flushROutput];
}

- (void) flushROutput {
    if (writeBuffer!=writeBufferPos) {
        [self writeConsoleDirectly:[NSString stringWithUTF8String:writeBuffer] withColor:[consoleColors objectAtIndex:iOutputColor]];
        writeBufferPos=writeBuffer;
    }
}

- (void) handleFlushConsole {
    [self flushROutput];
    [self flushStdConsole];
}

/* this writes R output to the Console window, but indirectly by using a buffer */
- (void) handleWriteConsole: (NSString*) txt {
    if (!txt) return;
    const char *s = [txt UTF8String];
    int sl = strlen(s);
    int fits = writeBufferLen-(writeBufferPos-writeBuffer)-1;
    
    // let's flush the buffer if the new string is large and it would, but the buffer should be occupied
    if (fits<sl && fits>writeBufferHighWaterMark) {
        // for efficiency we're not using handleFlushConsole, because that would trigger stdxx flush, too
        [self writeConsoleDirectly:[NSString stringWithUTF8String:writeBuffer] withColor:[consoleColors objectAtIndex:iOutputColor]];
        writeBufferPos=writeBuffer;
        fits = writeBufferLen-1;
    }
    
    while (fits<sl) {   // ok, we're in a situation where we must split the string
        memcpy(writeBufferPos, s, fits);
        writeBufferPos[writeBufferLen-1]=0;
        [self writeConsoleDirectly:[NSString stringWithUTF8String:writeBuffer] withColor:[consoleColors objectAtIndex:iOutputColor]];
        sl-=fits; s+=fits;
        writeBufferPos=writeBuffer;
        fits=writeBufferLen-1;
    }
    
    strcpy(writeBufferPos, s);
    writeBufferPos+=sl;
    
    // flush the buffer if the low watermark is reached
    if (fits-sl<writeBufferLowWaterMark) {
        [self writeConsoleDirectly:[NSString stringWithUTF8String:writeBuffer] withColor:[consoleColors objectAtIndex:iOutputColor]];
        writeBufferPos=writeBuffer;
    }
}

/* this writes R output to the Console window directly, i.e. without using a buffer. Use handleWriteConsole: for the regular way. */
- (void) writeConsoleDirectly: (NSString*) txt withColor: (NSColor*) color{
    if (!txt) return;
    NSRange scr = [txt rangeOfCharacterFromSet:specialCharacters];
    if (scr.location != NSNotFound) { /* at least one special character */
        int tl = [txt length], cl = scr.location;
        unichar sc = [txt characterAtIndex: cl];
        SLog(@"writeConsoleDirectly special char 0x%x (@%d of %d, full string:'%@')", (int) sc, cl, tl, txt);
        if (sc == '\r') { /* CR */
            if (tl < cl + 1 && [txt characterAtIndex: cl + 1] == '\n') { /* CR+LF -> can use as-is */
                if (tl > cl + 2) {
                    NSString *head = [txt substringToIndex: cl + 2];
                    NSString *tail = [txt substringFromIndex: cl + 2];
                    [self writeConsoleDirectly:head withColor:color];
                    [self writeConsoleDirectly:tail withColor:color];
                    return;
                }
            } else if (cl > 0 && [txt characterAtIndex:cl - 1] == '\n') { /* LF+CR -> ignore */
                NSString *head = [txt substringToIndex: cl];
                cl++; while (cl < tl && [txt characterAtIndex:cl] == '\r') cl++; /* skip all subsequent CRs */
                if (cl < tl) { /* is there more behind those CRs? Then we need to split-process it */
                    NSString *tail = [txt substringFromIndex: cl];
                    [self writeConsoleDirectly:tail withColor:color];
                    return;
                } else txt = head; /* just proceed with trailing CRs removed */
            } else { /* ok, a "true" CR without any of its LF friends */
                NSString *head = [txt substringToIndex: cl];
                cl++; while (cl < tl && [txt characterAtIndex:cl] == '\r') cl++; /* skip all subsequent CRs */
                NSString *tail = nil;
                if (cl < tl) tail = [txt substringFromIndex: cl];
                [self writeConsoleDirectly:head withColor:color];
                if (outputPosition < 0) outputPosition = [[consoleTextView textStorage] length];
                NSRange lr = [[[consoleTextView textStorage] string] lineRangeForRange:NSMakeRange(outputPosition, 0)];
                /* do whatever we need to do to mark CR */
                outputPosition = lr.location;
                outputOverwrite = lr.length;
                //[consoleTextView setSelectedRange:lr];
                if (tail) [self writeConsoleDirectly:tail withColor:color];
                return;
            }
        } else {
            if (cl > 0) {
                NSString *head = [txt substringToIndex: cl];
                SLog(@"write head '%@'", head);
                [self writeConsoleDirectly:head withColor:color];
            }
            NSRange csr = [consoleTextView selectedRange];
            if (outputPosition < 0) outputPosition = [[consoleTextView textStorage] length];
            [consoleTextView setSelectedRange:NSMakeRange(outputPosition, 0)];
            while (cl < tl) {
                unichar tsc = [txt characterAtIndex:cl];
                SLog(@" @%d: %d", cl, tsc);
                if (tsc == '\a')
                    NSBeep();
                else if (tsc == '\b' && outputPosition > 0) {
                    int ocl = committedLength;
                    committedLength = 0;
                    [consoleTextView deleteBackward:self];
                    committedLength = ocl;
                    if (outputPosition <= committedLength) committedLength--;
                    if (outputPosition <= promptPosition) promptPosition--;
                    if (outputPosition <= csr.location) csr.location--;
                    outputPosition--;
                }
                else break;
                cl++;
            }
            [consoleTextView setSelectedRange:csr];
            if (cl < tl) {
                NSString *tail = [txt substringFromIndex: cl];
                SLog(@"process tail '%@'", tail);
                [self writeConsoleDirectly:tail withColor:color];
            }
            return;
        }
    }
    BOOL inEditing = NO;
    @try {
        @synchronized(textViewSync) {
            RConsoleTextStorage *textStorage = (RConsoleTextStorage*) [consoleTextView textStorage];
            NSRange origSel = [consoleTextView selectedRange];
            unsigned tl = [txt length];
            int delta = 0;
            if (tl>0) {
                unsigned oldCL=committedLength;
                SLog(@"original: %d:%d, insertion: %d, length: %d, prompt: %d, commit: %d, overwrite:%d", origSel.location,
                     origSel.length, outputPosition, tl, promptPosition, committedLength, outputOverwrite);
                if (outputPosition > [textStorage length]) outputPosition = [textStorage length];
                SLog(@"RController writeConsoleDirectly, beginEditing");
                [textStorage beginEditing];
                inEditing = YES;
                committedLength=0;
                if (outputOverwrite) {
                    int otl = [txt length];
                    NSRange nlr = [txt rangeOfString:@"\n"]; /* if it has any newlines, we replace only up to the newline */
                    if (nlr.location != NSNotFound) {
                        int nlpos = nlr.location;
                        nlr.length = (nlpos < outputOverwrite) ? nlpos : outputOverwrite;
                        nlr.location = outputPosition;
                        [textStorage replaceCharactersInRange:nlr withString:@""];
                        [textStorage insertText:[txt substringToIndex:nlpos] atIndex:outputPosition withColor:color];
                        delta = nlpos - outputOverwrite;
                        txt = [txt substringFromIndex:nlr.length];
                        tl = [txt length];
                        outputPosition += outputOverwrite;
                        outputOverwrite = 0;
                    } else {
                        if (otl > outputOverwrite) otl = outputOverwrite;
                        nlr.location = outputPosition;
                        nlr.length = otl;
                        [textStorage replaceCharactersInRange:nlr withString:@""];
                        outputOverwrite -= otl;
                        delta = -nlr.length;
                    }
                }
                [textStorage insertText:txt atIndex:outputPosition withColor:color];
                if (outputPosition <= promptPosition) promptPosition += tl + delta;
                committedLength=oldCL;
                if (outputPosition <= committedLength) committedLength += tl + delta;
                if (outputPosition <= origSel.location) origSel.location += tl + delta;
                outputPosition += tl;
                [textStorage endEditing];
                inEditing = NO;
                SLog(@"RController writeConsoleDirectly, endEditing");
                [consoleTextView setSelectedRange:origSel];
                [consoleTextView scrollRangeToVisible:origSel];
            }
        }
    }
    @catch (NSException *e) {
        SLog(@"** EXCEPTION while editing console: %@", e);
        if (inEditing) [[consoleTextView textStorage] endEditing];
    }
}

/* Just writes the prompt in a different color */
- (void)handleWritePrompt: (NSString*) prompt {
    [self handleFlushConsole];
    outputOverwrite=0; // disable any overwrites
    @synchronized(textViewSync) {
        RConsoleTextStorage *textStorage = (RConsoleTextStorage*) [consoleTextView textStorage];
        unsigned textLength = [textStorage length];
        int promptLength=[prompt length];
//      NSLog(@"Prompt: %@", prompt);
        NSRange lr = [[textStorage string] lineRangeForRange:NSMakeRange(textLength,0)];
        SLog(@"RController handleWritePrompt: '%@', beginEditing", prompt);
        [textStorage beginEditing];
        promptPosition=textLength;
        if (lr.location!=textLength) { // the prompt must be on the beginning of the line
            [textStorage insertText: @"\n" atIndex: textLength withColor:[consoleColors objectAtIndex:iPromptColor]];
            textLength = [textStorage length];
            promptLength++;
        }
        
        if (promptLength>0) {
            [textStorage insertText:prompt atIndex: textLength withColor:[consoleColors objectAtIndex:iPromptColor]];
            if (promptLength>1) // this is a trick to make sure that the insertion color doesn't change at the prompt
                [textStorage addAttribute:@"NSColor" value:[consoleColors objectAtIndex:iInputColor] range:NSMakeRange(promptPosition+promptLength-1, 1)];
            committedLength=promptPosition+promptLength;
        }
        committedLength=promptPosition+promptLength;
        [textStorage endEditing];
        SLog(@"RController handleWritePrompt: '%@', endEditing", prompt);
        {
            NSRange targetRange = NSMakeRange(committedLength,0);
            [consoleTextView setSelectedRange:targetRange];
            [consoleTextView scrollRangeToVisible:targetRange];
        }
    }
}

- (void)  handleProcessingInput: (char*) cmd
{
    NSString *s = [[NSString alloc] initWithUTF8String:cmd];
    
    @synchronized(textViewSync) {
        unsigned textLength = [[consoleTextView textStorage] length];
        
        [consoleTextView setSelectedRange:NSMakeRange(committedLength, textLength-committedLength)];
        [consoleTextView insertText:s];
        textLength = [[consoleTextView textStorage] length];
        [consoleTextView setTextColor:[consoleColors objectAtIndex:iInputColor] range:NSMakeRange(committedLength, textLength-committedLength)];
        outputPosition=committedLength=textLength;
        
        // remove undo actions to prevent undo across prompts
        [[consoleTextView undoManager] removeAllActions];
    }
    
    [s release];
    
// the ?/help hack is no longer needed in 2.1
#if (R_VERSION < R_Version(2,1,0))
    if((*cmd == '?') || (!strncmp("help(",cmd,5))){ 
        [self openHelpFor: cmd];
        cmd[0] = '\n'; cmd[1] = 0;
    }
#endif
}

- (char*) handleReadConsole: (int) addtohist
{
#ifdef USE_POOLS
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
#endif
    
    if (currentConsoleInput) {
        [currentConsoleInput release];
        currentConsoleInput=nil;
    }
    
    while ([consoleInputQueue count]==0) {
        processingEvents = NO; // we should be at the top level, so for sanity reasons make sure we always process events
        [self doProcessEvents: YES];
    }
    
    currentConsoleInput = [consoleInputQueue objectAtIndex:0];
    [consoleInputQueue removeObjectAtIndex:0];
    
    if (addtohist) {
//      Figure out how to get hold of ParseStatus here!
        [hist commit:currentConsoleInput];
        [historyView reloadData];
    }
    
    {
        const char *c = [currentConsoleInput UTF8String];
#ifdef USE_POOLS

        if (!c) { [pool release]; return 0; }
#endif
        if (strlen(c)>readConsTransBufferSize-1) { // grow as necessary
            free(readConsTransBuffer);
            readConsTransBufferSize = (strlen(c)+2048)&0xfffffc00;
            readConsTransBuffer = (char*) malloc(readConsTransBufferSize);
        } // we don't shrink the buffer if gets too large - we may want to think about that ...
        
        strcpy(readConsTransBuffer, c);
    }
#ifdef USE_POOLS
    [pool release];
#endif
    return readConsTransBuffer;
}

- (int) handleEdit: (char*) file
{
#ifdef USE_POOLS
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
#endif
    NSString *fn = [NSString stringWithUTF8String:file];
    if (fn) fn = [fn stringByExpandingTildeInPath];
    if (!fn) Rf_error("Invalid file name.");

    SLog(@"RController.handleEdit: %s", file);
    
    if (![[NSFileManager defaultManager] isReadableFileAtPath:fn]) {
        //[pool release];
        return 0;
    }
    NSURL *url = [[NSURL alloc] initFileURLWithPath:fn];
    NSError *theError;
    RDocument *document = [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:url display:YES error:&theError];
    [document setREditFlag: YES];
    
    NSArray *wcs = [document windowControllers];
    if ([wcs count]<1) {
        SLog(@"handleEdit: WARNING, no window controllers for newly created document!");
    } else {
        NSWindowController *wc = (NSWindowController*)[wcs objectAtIndex:0];
        NSWindow *win = [wc window];
        if (win) [NSApp runModalForWindow:win];
        else { SLog(@"handleEdit: WARNING, window is null!"); };
        if ([wcs count]>1) {
            SLog(@"handleEdit: WARNING, there is more than one window controller, ignoring all but the first one.");
        }
    }
    
#ifdef USE_POOLS
    [pool release];
#endif
    return(0);
}

/* FIXME: the filename is not set for newly created files */
- (int) handleEditFiles: (int) nfile withNames: (char**) file titles: (char**) wtitle pager: (char*) pager
{
    int     i;
    
    SLog(@"RController.handleEditFiles (%d of them, pager %s)", nfile, pager);
    if (nfile <=0) return 1;
    
    for (i = 0; i < nfile; i++) {
        NSString *fn = [NSString stringWithUTF8String:file[i]];
        if (fn) fn = [fn stringByExpandingTildeInPath];
        SLog(@"file #%d: %@", i + 1, fn);
        if (fn) {
            if([[NSFileManager defaultManager] fileExistsAtPath:fn]) {
                NSURL *url = [[NSURL alloc] initFileURLWithPath:fn];
                NSError *theError;
                [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:url display:YES error:&theError];
            } else
                [[NSDocumentController sharedDocumentController] newDocument: [RController sharedController]];
            
            NSDocument *document = [[NSDocumentController sharedDocumentController] currentDocument];
            if(wtitle[i]!=nil)
                [RDocument changeDocumentTitle: document Title: [NSString stringWithUTF8String:wtitle[i]]];
        }
    }
    return 1;
}

- (int) handleShowFiles: (int) nfile withNames: (char**) file headers: (char**) headers windowTitle: (char*) wtitle pager: (char*) pages andDelete: (BOOL) del
{
    int     i;
    
    if (nfile <=0) return 1;
    SLog(@"RController.handleShowFiles (%d of them, title %s, pager %s)", nfile, wtitle, pages);
    
    for (i = 0; i < nfile; i++){
        NSString *fn = [NSString stringWithUTF8String:file[i]];
        if (fn) fn =[fn stringByExpandingTildeInPath];
        if (fn) {
            NSURL *url = [[NSURL alloc] initFileURLWithPath:fn];
            NSError *theError;
            RDocument *document = [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:url display:YES error:&theError];
            // don't display - we need to prevent the window controller from using highlighting
            if (document) {
                NSArray *wcs = [document windowControllers];
                if (wcs && [wcs count]>0) {
                    SLog(@" - Disabling syntax highlighting for this document");
                    [(RDocumentWinCtrl*) [wcs objectAtIndex:0] setPlain:YES];
                }
                if (wtitle)
                    [RDocument changeDocumentTitle: document Title: [NSString stringWithUTF8String:wtitle]];
                [document setEditable: NO];
                SLog(@" - finally show the document window");
                [document showWindows];
            }
        }
    }
    return 1;
}

//======== Cocoa Handler ======

- (int) handlePackages: (int) count withNames: (char**) name descriptions: (char**) desc URLs: (char**) url status: (BOOL*) stat
{
    [[PackageManager sharedController] updatePackages:count withNames:name descriptions:desc URLs:url status:stat];
    return 0;
}

- (int) handleListItems: (int) count withNames: (char**) name status: (BOOL*) stat multiple: (BOOL) multiple title: (NSString*) title;
{
    return [[SelectList sharedController] selectList:count withNames:name status:stat multiple: multiple title:title];
}

- (int) handleHelpSearch: (int) count withTopics: (char**) topics packages: (char**) pkgs descriptions: (char**) descs urls: (char**) urls title: (char*) title
{
    [[SearchTable sharedController] updateHelpSearch:count withTopics:topics packages:pkgs descriptions:descs urls:urls title:title];
    return 0;
}

- (BOOL*) handleDatasets: (int) count withNames: (char**) name descriptions: (char**) desc packages: (char**) pkg URLs: (char**) url
{
    [[DataManager sharedController] updateDatasets:count withNames:name descriptions:desc packages:pkg URLs:url];
    return 0; // we don't load the DS this way, we use REngine instead
}

- (int) handleInstalledPackages: (int) count withNames: (char**) name installedVersions: (char**) iver repositoryVersions: (char**) rver update: (BOOL*) stat label: (char*) label
{
    [[PackageInstaller sharedController] updateInstalledPackages:count withNames:name installedVersions:iver repositoryVersions:rver update:stat label:label];
    return 0;
}

- (int) handleSystemCommand: (char*) cmd
{   
    int cstat=-1;
    pid_t pid;
    
    if ([self getRootFlag]) {
        FILE *f;
        char *argv[3] = { "-c", cmd, 0 };
        int res;
        NSBundle *b = [NSBundle mainBundle];
        char *sushPath = 0;
        if (b) {
            NSString *sush = [[b resourcePath] stringByAppendingString:@"/sush"];
            sushPath = strdup([sush UTF8String]);
        }
        
        res = runRootScript(sushPath?sushPath:"/bin/sh",argv,&f,1);
        if (!res && f) {        
            int fd = fileno(f);
            if (fd != -1) {
                struct timespec peSleep = { 0, 50000000 }; // 50ms sleep
                [self setRootFD:fileno(f)];
            
                while (rootFD!=-1) { // readThread will reset rootFD to -1 when reaching EOF
                    nanosleep(&peSleep, 0); // sleep at least 50ms between PE calls (they're expensive)
                    Re_ProcessEvents();
                }
            }
        }
        if (sushPath) free(sushPath);
        return res;
    }
    
    pid=fork();
    if (pid==0) {
        // int sr;
        // reset signal handlers
        signal(SIGINT, SIG_DFL);
        signal(SIGTERM, SIG_DFL);
        signal(SIGQUIT, SIG_DFL);
        signal(SIGALRM, SIG_DFL);
        signal(SIGCHLD, SIG_DFL);
        execl("/bin/sh", "/bin/sh", "-c", cmd, NULL);
        exit(-1);
        //sr=system(cmd);
        //exit(WEXITSTATUS(sr));
    }
    if (pid==-1) return -1;
        
    {
        while (1) {
            pid_t w = waitpid(pid, &cstat, WNOHANG);
            if (w!=0) break;
            // NOTE: this deliberately circumvents doProcessEvents: since we need events
            // to be processed in all cases, even if system was called from within
            // the event handler and as such can run recursively
            NSEvent *event;
            if( (event = [NSApp nextEventMatchingMask:NSAnyEventMask
                                            untilDate:[NSDate dateWithTimeIntervalSinceNow:0.05]
                                               inMode:NSDefaultRunLoopMode 
                                              dequeue:YES]))
                [NSApp sendEvent:event];
        }
    }
    [[RController sharedController] rmChildProcess: pid];
    return cstat;
}   

- (int) handleCustomPrint: (char*) type withObject: (RSEXP*) obj
{
    if (!obj) return -2;
    if (!strcmp(type, "help-files")) {
        RSEXP *x = [obj attr:@"topic"];
        NSString *topic = @"<unknown>";
        if (x && [x string]) topic = [x string];
        [x release];
        if ([obj type]==STRSXP && [obj length]>0) {
            [[HelpManager sharedController] showHelpUsingFile: [obj string] topic: topic];
            [[self window] makeKeyWindow];
            [[[HelpManager sharedController] window] makeKeyAndOrderFront:self];
        } else {
            NSBeginAlertSheet(
                NLS(@"Help topic not found"),
                NLS(@"OK"),
                nil,
                nil,
                [consoleTextView window],
                self,
                @selector(sheetDidEnd:returnCode:contextInfo:),
                @selector(sheetDidEnd:returnCode:contextInfo:),
                NULL,
                [NSString stringWithFormat: NLS(@"Help for the topic \"%@\" was not found."), topic]);
        }
    }
    return 0;
}

-(void)sheetDidEnd:(NSWindow *)sheet returnCode: (int)returnCode 
        contextInfo: (void *)contextInfo {
    [[RController sharedController] makeConsoleKey:self];
}

//==========

- (void)didCloseAll:(id)sender {
    [Preferences commit];
    [self doSaveHistory:nil];
    NSString *sa = [Preferences stringForKey:@"saveOnExit" withDefault:@"ask"];
    if ([sa isEqualToString: @"ask"])
        NSBeginAlertSheet(NLS(@"Closing R session"),NLS(@"Save"),NLS(@"Don't Save"),NLS(@"Cancel"),
            [consoleTextView window],self,@selector(shouldCloseDidEnd:returnCode:contextInfo:),NULL,NULL,
            NLS(@"Save workspace image?"));
    else {
        terminating = YES;
        [self windowShouldClose:self];
    }
}

- (BOOL)windowShouldClose:(id)sender
{
    if (!terminating) {
        SLog(@"RController.windowShouldClose: initiating app termination");
        [[NSApplication sharedApplication] terminate:self];
        SLog(@"RController.windowShouldClose: app termination finished.");
        return NO;
    }
//  NSString *sa = [[REngine mainEngine] saveAction];
    NSString *sa = requestSaveAction ? requestSaveAction : [Preferences stringForKey:saveOnExitKey withDefault:@"ask"];
    SLog(@"RController.windowShouldClose: save action is %@.", sa);
    if ([sa isEqual:@"yes"] || [sa isEqual:@"no"]) {
        [Preferences commit];
        [self doSaveHistory:nil];
        [[REngine mainEngine] executeString:[NSString stringWithFormat:@"base::quit('%@')", sa]];
        return YES;
    }
    //[[RDocumentController sharedDocumentController] closeAllDocumentsWithDelegate:self didCloseAllSelector:@selector(didCloseAll:) contextInfo:nil];  
    //return NO;
    [self didCloseAll:self];
    SLog(@"RController.windowShouldClose: running modal");
    BOOL canClose = (BOOL)[[NSApplication sharedApplication] runModalForWindow:RConsoleWindow];
    SLog(@"RController.windowShouldClose: returning %@", canClose?@"YES":@"NO");
    // FWIW: canClose is never YES, because didCloseAll: executes quit(..)
    return canClose;
}   
    
/* this gets called by the "wanna save?" sheet on window close */
- (void) shouldCloseDidEnd:(NSWindow *)sheet returnCode:(int)returnCode 
        contextInfo:(void *)contextInfo {
    [Preferences commit];
    // the code specifies whether it's ok for the application to close in response to windowShouldClose:
    [[NSApplication sharedApplication] stopModalWithCode: 
        (returnCode==NSAlertDefaultReturn || returnCode==NSAlertAlternateReturn)?YES:NO];
    if (returnCode==NSAlertDefaultReturn)
        [[REngine mainEngine] executeString:@"base::quit(\"yes\")"];
    if (returnCode==NSAlertAlternateReturn)
        [[REngine mainEngine] executeString:@"base::quit(\"no\")"];
}

/*  This is used to send commands through the GUI, i.e. from menus 
The input replaces what the user is currently typing.
*/
- (void) sendInput: (NSString*) text {
    [self consoleInput:text interactive:YES];
}

/* These two routines are needed to update the History TableView */
- (int)numberOfRowsInTableView: (NSTableView *)tableView
{
    return [[hist entries] count];
}

- (id)tableView: (NSTableView *)tableView
        objectValueForTableColumn: (NSTableColumn *)tableColumn
            row: (int)row
{
    return (NSString*) [[hist entries]  objectAtIndex: row];
}

/*  Clears the history  and updates the TableView */

- (IBAction)doClearHistory:(id)sender
{
    SLog(@"RController.doClearHistory");
    [hist resetAll];
    [historyView reloadData];
}

- (IBAction)doLoadHistory:(id)sender
{
    NSString *fname=nil;
    SLog(@"RController.doLoadHistory");
    if (sender) {
        NSOpenPanel *op = [NSOpenPanel openPanel];
        [op setDirectory:[[[NSFileManager defaultManager] currentDirectoryPath] stringByExpandingTildeInPath]];
        [op setTitle:NLS(@"Choose history File")];
        if([op runModalForTypes: [NSArray arrayWithObject:@"history"]] == NSOKButton)
            fname = [op filename];
    } else
        fname = [[Preferences stringForKey:historyFileNamePathKey
                               withDefault: kDefaultHistoryFile] stringByExpandingTildeInPath];
    SLog(@" - history file to load: %@", fname);
    if(fname != nil){
//      fname = [[[[NSFileManager defaultManager] currentDirectoryPath] stringByExpandingTildeInPath] stringByAppendingString:[NSString stringWithFormat:@"/%@", fname]];
        [self doClearHistory:nil];
        
        SLog(@" - cleared history, reload with: %@", fname);
        if ([[NSFileManager defaultManager] fileExistsAtPath: fname]) {
            FILE *rhist = fopen([fname UTF8String], "r");
            if (!rhist) {
                NSLog(NLS(@"Can't open history file %@"), fname);
            } else {
                NSString *entry = nil;
                char c[1024];
                
                c[1023]=0;
                while(fgets(c, 1023, rhist) && *c) {
                    int i = strlen(c);
                    BOOL multiline = NO;
                    NSString *sEntry;

                    while (i>0 && (c[i-1]=='\n' || c[i-1]=='\r')) c[--i]=0; // just in case someone has PC history we strip \r too
                    if (!*c) continue; // skip blank lines (is that intended? what about "foo#\n\nbla\n"?)
                    if (multiline=(c[i-1]=='#')) c[i-1]='\n';
                    sEntry=[NSString stringWithUTF8String:c];
                    if (sEntry) {
                        if (entry)
                            entry = [entry stringByAppendingString:sEntry];
                        else
                            entry = sEntry;
                        if (!multiline) {
                            [hist commit:entry];
                            entry=nil;
                        }
                    }
                }
                if (entry) [hist commit:entry]; // just being paranoid if someone edited the file manually
                fclose(rhist);
            }
        }       
        [historyView scrollRowToVisible:[historyView numberOfRows]];
        [historyView reloadData];
    }
}

- (void)doSaveHistory:(id)sender {
    NSString *fname = nil;
    FILE *rhist;
    
    if (sender) {
        NSSavePanel *sp = [NSSavePanel savePanel];
        [sp setDirectory:[[[NSFileManager defaultManager] currentDirectoryPath] stringByExpandingTildeInPath]];
        [sp setRequiredFileType:@"history"];
        [sp setTitle:NLS(@"Save history File")];
        if([sp runModal] == NSOKButton) fname = [sp filename];      
    } else 
        fname = [[Preferences stringForKey:historyFileNamePathKey
                               withDefault: kDefaultHistoryFile] stringByExpandingTildeInPath];

    SLog(@"RController.doSaveHistory (file %@)", fname);
    rhist = fopen([fname UTF8String], "w");
    if (!rhist) {
        SLog(@"* Can't create history file %@", fname);
    } else {
        NSEnumerator *enumerator = [[hist entries] objectEnumerator];
        NSString *entry;
        
        while (entry = [enumerator nextObject]) {
            if ([entry rangeOfString:@"\n" options:NSLiteralSearch].location!=NSNotFound) { // add # before \n for multi-line strings
                entry = [entry mutableCopy];
                [(NSMutableString*)entry replaceOccurrencesOfString:@"\n" withString:@"#\n" options:NSLiteralSearch range:NSMakeRange(0,[entry length])];
            }
            fputs([entry UTF8String], rhist); // not 100% safe
            fputc('\n', rhist); // trailing \n
        }

        fclose(rhist);  
    }
}

/*  On double-click on items of the History TableView, the item is pasted into the console
at current cursor position
*/
- (IBAction)historyDoubleClick:(id)sender {
    NSString *cmd;
    int index = [historyView selectedRow];
    if(index == -1) return;
    
    cmd = [[hist entries] objectAtIndex:index];
    [self consoleInput:cmd interactive:NO];
    [RConsoleWindow makeFirstResponder:consoleTextView];
}

- (IBAction)historyDeleteEntry:(id)sender {
    int index = [historyView selectedRow];
    if((index >= 0) && (index < [[hist entries] count]))
        [hist deleteEntry:index];
    [historyView reloadData];
}

/*  This routine is intended to "cat" some text to the R Console without
issuing the newline.
- (void) consolePaste: (NSString*) text {
    unsigned textLength = [[consoleTextView textStorage] length];
    [consoleTextView setSelectedRange:NSMakeRange(textLength, 0)];
    [consoleTextView insertText:text];
}
*/
/* This function is used by two threads to write  stderr and/or stdout to the console
length: -1 = the string is null-terminated
outputType: 0 = stdout, 1 = stderr, 2 = stdout/err as root
*/
- (void) writeLogsWithBytes: (char*) buf length: (int) len type: (int) outputType
{
    NSColor *color=(outputType==0)?[consoleColors objectAtIndex:iStdoutColor]:((outputType==1)?[consoleColors objectAtIndex:iStderrColor]:[consoleColors objectAtIndex:iRootColor]);
    if (len>=0 && buf[len]!=0) buf[len]=0; /* this MAY be dangerous ... */
    NSString *s = [[NSString alloc] initWithUTF8String:buf];
    [self flushROutput];
    [self writeConsoleDirectly:s withColor:color];
    [s release];
}

+ (RController*) sharedController{
    return sharedRController;
}

/* console input - the string passed here is handled as if it was typed on the console */
- (void) consoleInput: (NSString*) cmd interactive: (BOOL) inter
{
    @synchronized(textViewSync) {
        if (!inter) {
            int textLength = [[consoleTextView textStorage] length];
            if (textLength>committedLength)
                [consoleTextView replaceCharactersInRange:NSMakeRange(committedLength,textLength-committedLength) withString:@""];
            [consoleTextView setSelectedRange:NSMakeRange(committedLength,0)];
            [consoleTextView insertText: cmd];
            textLength = [[consoleTextView textStorage] length];
            [consoleTextView setTextColor:[consoleColors objectAtIndex:iInputColor] range:NSMakeRange(committedLength,textLength-committedLength)];
        }
        
        if (inter) {
            if ([cmd characterAtIndex:[cmd length]-1]!='\n') cmd=[cmd stringByAppendingString: @"\n"];
            [consoleInputQueue addObject:[[NSString alloc] initWithString:cmd]];
            [self setStatusLineText:@""];
        }
    }
}

- (BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector {
    BOOL retval = NO;
    
    SLog(@"RController.textView doCommandBySelector: %@\n", NSStringFromSelector(commandSelector));
    
    if (@selector(insertNewline:) == commandSelector) {
        unsigned textLength = [[textView textStorage] length];
        [textView setSelectedRange:NSMakeRange(textLength,0)];
        if (textLength >= committedLength) {
            [textView insertText:@"\n"];
            textLength = [[textView textStorage] length];
            [self consoleInput: [[textView attributedSubstringFromRange:NSMakeRange(committedLength, textLength - committedLength)] string] interactive: YES];
            return(YES);
        }
        retval = YES;
    }

    // ---- history browsing ----
    if (@selector(moveUp:) == commandSelector) {
        unsigned textLength = [[textView textStorage] length];        
        NSRange sr=[textView selectedRange];
        if (sr.location==committedLength || sr.location==textLength) {
            NSRange rr=NSMakeRange(committedLength, textLength-committedLength);
            NSString *text = [[textView attributedSubstringFromRange:rr] string];
            if ([hist isDirty]) {
                [hist updateDirty: text];
            }
            NSString *news = [hist prev];
            if (news!=nil) {
                [news retain];
                sr.length=0; sr.location=committedLength;
                [textView setSelectedRange:sr];
                [textView replaceCharactersInRange:rr withString:news];
                [textView insertText:@""];
                [news release];
            }
            retval = YES;
        }
    }
    if (@selector(moveDown:) == commandSelector) {
        unsigned textLength = [[textView textStorage] length];        
        NSRange sr=[textView selectedRange];
        if ((sr.location==committedLength || sr.location==textLength) && ![hist isDirty]) {
            NSRange rr=NSMakeRange(committedLength, textLength-committedLength);
            NSString *news = [hist next];
            if (news==nil) news=@""; else [news retain];
            sr.length=0; sr.location=committedLength;
            [textView setSelectedRange:sr];
            [textView replaceCharactersInRange:rr withString:news];
            [textView insertText:@""];
            [news release];
            retval = YES;
        }
    }
    
    // ---- make sure the user won't accidentally get out of the input line ----
    // FIXME: essentially everything here behaves the same for a paragraph and line, but we should be making a distinction because of multi-line commands
    if (@selector(moveToBeginningOfParagraph:) == commandSelector || @selector(moveToBeginningOfLine:) == commandSelector) {
        [textView setSelectedRange: NSMakeRange(committedLength,0)];
        retval = YES;
    }
    
    if (@selector(deleteToBeginningOfLine:) == commandSelector || @selector(deleteToBeginningOfParagraph:) == commandSelector) {
        NSRange r = [textView selectedRange];
        if (r.length) /* if anything is selected, only the selected portion gets killed */
            [textView insertText:@""];
        else { /* otherwise delete all up to the beginning of the line or commit point */
            r.length = r.location - committedLength;
            r.location = committedLength;
            if (r.length > 0) {
                [textView setSelectedRange:r];
                [textView insertText:@""];
            }
        }
        retval = YES;
    }
    
    if (@selector(moveToBeginningOfParagraphAndModifySelection:) == commandSelector || @selector(moveToBeginningOfLineAndModifySelection:) == commandSelector) {
        NSRange r = [textView selectedRange];
        r.length = r.location + r.length - committedLength;
        r.location = committedLength;
        if (r.length < 0) r.length = 0;
        [textView setSelectedRange: r];
        retval = YES;
    }
    
    if (@selector(moveWordLeft:) == commandSelector || @selector(moveLeft:) == commandSelector ||
        @selector(moveWordLeftAndModifySelection:) == commandSelector || @selector(moveLeftAndModifySelection:) == commandSelector) {
        NSRange sr=[textView selectedRange];
        if (sr.location==committedLength) return YES;
    }
    
    // ---- code/file completion ----
    
    if (@selector(insertTab:) == commandSelector) {
        [textView complete:self];
        retval = YES;
    }
    
    // ---- cancel ---
    
    if (@selector(cancel:) == commandSelector || @selector(cancelOperation:) == commandSelector) {
        [self breakR:self];
        retval = YES;
    }
    
    return retval;
}

- (void)textStorageDidProcessEditing:(NSNotification *)aNotification {
    NSTextStorage *ts = [aNotification object];
    NSString *s = [ts string];
    NSRange sr = [consoleTextView selectedRange];

    // check for a typed (
    if (argsHints && sr.location>committedLength && sr.length==0 && sr.location>0 && sr.location<[s length] && [s characterAtIndex:sr.location]=='(') {
        int i = sr.location-1;
        unichar c = [s characterAtIndex:i];
        BOOL hasLit = NO;
        while ((c>='0' && c<='9') || (c>='a' && c<='z') || (c>='A' && c<='Z') || c=='.' || c=='_') {
            if (!hasLit && ((c>='a' && c<='z') || (c>='A' && c<='Z'))) hasLit=YES;
            i--;
            if (i<0) break;
            c = [s characterAtIndex:i];
        }
        i++;
        if (hasLit && sr.location>i)
            [self hintForFunction: [s substringWithRange:NSMakeRange(i,sr.location-i)]];
    }
}

- (BOOL) hintForFunction: (NSString*) fn
{
    BOOL success = NO;
    if (insideR>0) {
        [self setStatusLineText:NLS(@"(arguments lookup is disabled while R is busy)")];
        return NO;
    }
    if (![[REngine mainEngine] beginProtected]) {
        [self setStatusLineText:NLS(@"(arguments lookup is disabled while R is busy)")];
        return NO;      
    }
    RSEXP *x = [[REngine mainEngine] evaluateString:[NSString stringWithFormat:@"try(gsub('\\\\s+',' ',paste(capture.output(print(args(%@))),collapse='')),silent=TRUE)", fn]];
    if (x) {
        NSString *res = [x string];
        if (res && [res length]>10 && [res hasPrefix:@"function"]) {
            if ([res hasSuffix:@" NULL"]) res=[res substringToIndex:[res length]-5];
            res = [fn stringByAppendingString:[res substringFromIndex:9]];
            success = YES;
            [self setStatusLineText:res];
        }
        [x release];
    }
    [[REngine mainEngine] endProtected];
    return success;
}

/* Allow changes only for uncommitted text */
- (BOOL)textView:(NSTextView *)textView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString {
    if (replacementString && /* on font change we get nil replacementString which is ok to pass through */
        affectedCharRange.location < committedLength) { /* if the insertion is outside editable scope, append at the end */
        [textView setSelectedRange:NSMakeRange([[textView textStorage] length],0)];
        [textView insertText:replacementString];
        return NO;
    }
    return YES;
}

- (NSArray *)textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(int *)index 
{
    NSRange sr=[textView selectedRange];
    SLog(@"completion attempt; cursor at %d, complRange: %d-%d, commit: %d", sr.location, charRange.location, charRange.location+charRange.length, committedLength);
    //sr=charRange;
    int bow = sr.location + sr.length;
    if (bow > committedLength) {
        while (bow>committedLength) bow--;
        {
            NSString *rep=nil;
            NSRange er = NSMakeRange(bow,sr.location+sr.length-bow);
            NSString *text = [[textView attributedSubstringFromRange:er] string];
            
            // first we need to find out whether we're in a text part or code part
            unichar c;
            int tl = [text length], tp=0, quotes=0, dquotes=0, lastQuote=-1;
            while (tp<tl) {
                c=[text characterAtIndex:tp];
                if (c=='\\') tp++; // skip the next char after a backslash (we don't have to worry about \023 and friends)
                else {
                    if (dquotes==0 && c=='\'') {
                        quotes^=1;
                        if (quotes) lastQuote=tp;
                    }
                    if (quotes==0 && c=='"') {
                        dquotes^=1;
                        if (dquotes) lastQuote=tp;
                    }
                }
                tp++;
            }
            
            if (quotes+dquotes>0) { // if we're inside any quotes, use file completion
                //rep=[FileCompletion complete:[text substringFromIndex:lastQuote+1]];
                er.location+=lastQuote+1;
                er.length-=lastQuote+1;
                return [FileCompletion completeAll:[text substringFromIndex:lastQuote+1] cutPrefix:0];
            } else { // otherwise use code completion
                int s = [text length]-1;
                c = [text characterAtIndex:s];
                while (((c>='a')&&(c<='z'))||((c>='A')&&(c<='Z'))||((c>='0')&&(c<='9'))||c=='.') {
                    s--;
                    if (s==-1) break;
                    c = [text characterAtIndex:s];
                }
                s++;
                er.location+=s; er.length-=s;
                //rep=[CodeCompletion complete:[text substringFromIndex:s]];
                *index=0;
                {
                    NSArray *ca = [CodeCompletion completeAll:[text substringFromIndex:s] cutPrefix:charRange.location-er.location];
                    if (ca && [ca count]==1) [self hintForFunction:[[ca objectAtIndex:0] substringToIndex:[(NSString*)[ca objectAtIndex:0] length]-1]];
                    return ca;
                }
            }
            
            // ok, by now we should get "rep" if completion is possible and "er" modified to match the proper part
            if (rep!=nil) {
                *index=0;
                return [NSArray arrayWithObjects: rep, @"dummy", nil];
                //[textView replaceCharactersInRange:er withString:rep];
            }
        }
    }
    return nil;
}

- (void) handleBusy: (BOOL) isBusy {
    if (isBusy)
        [progressWheel startAnimation:self];
    else
        [progressWheel stopAnimation:self];
    
    busyRFlag = isBusy;
    if (toolbarStopItem) {
        if (isBusy || childPID>0)
            [toolbarStopItem setEnabled:YES];
        else
            [toolbarStopItem setEnabled:NO];
    }
}

- (void)  handleShowMessage: (char*) msg
{
    NSRunAlertPanel(NLS(@"R Message"),[NSString stringWithUTF8String:msg],NLS(@"OK"),nil,nil);
}

- (IBAction)flushconsole:(id)sender {
    [self handleFlushConsole];
}

- (IBAction)otherEventLoops:(id)sender {
    R_runHandlers(R_InputHandlers, R_checkActivity(0, 1));
}


- (IBAction)newQuartzDevice:(id)sender {
    NSString *cmd;
    BOOL flag=[Preferences flagForKey:useQuartzPrefPaneSettingsKey withDefault: NO];
    if (flag) {
        NSString *width = [Preferences stringForKey:quartzPrefPaneWidthKey withDefault: @"4.5"];
        NSString *height = [Preferences stringForKey:quartzPrefPaneHeightKey withDefault: @"4.5"];
        cmd = [NSString stringWithFormat:@"quartz(width=%@,height=%@)", width, height];
    }
    else
        cmd = @"quartz()";
    
    [[REngine mainEngine] executeString:cmd];
}

- (IBAction)breakR:(id)sender{
    if (childPID)
        kill(childPID, SIGINT);
    else
        breakPending = YES;
    // we cannot break immediately in case we're in the middle of an inner event loop processing.
    // therefore we delay the break until the event loop is back (see doProcessEvents:)
}

- (IBAction)quitR:(id)sender{
    [self windowShouldClose:RConsoleWindow];
}

- (IBAction)makeConsoleKey:(id)sender
{
    [RConsoleWindow makeKeyAndOrderFront:sender];
}

- (IBAction)makeLastQuartzKey:(id)sender
{
    NSWindow *w = [(RDocumentController*)[NSDocumentController sharedDocumentController] findLastDocType:ftQuartz];
    NSDocument *d = [[NSDocumentController sharedDocumentController] documentForWindow:w];
    if (!d || ![d fileType] || ![[d fileType] isEqualToString:ftQuartz]) {
        /* could not find Quartz window - either there is none or it has never
        been key. That can happen, because new Quartz windows are not made
        key upon creation to not disturb the workflow (you can't do anything
                                                       with them anyway) */
        d = nil;
        NSArray *a = [[NSDocumentController sharedDocumentController] documents];
        int i = 0, ct = [a count];
        while (i < ct) {
            NSString *ft = [(NSDocument*)[a objectAtIndex:i] fileType];
            if (ft && [ft isEqualToString:ftQuartz]) d = (NSDocument*)[a objectAtIndex:i];
            i++;
        }
        if (d) {
            a = [d windowControllers];
            if (a && [a count]>0)
                w = [(NSWindowController*)[a objectAtIndex:0] window];
        }
    }
    [w makeKeyAndOrderFront:self];
}

- (IBAction)makeLastEditorKey:(id)sender
{
    [[(RDocumentController*)[NSDocumentController sharedDocumentController] findLastDocType:ftRSource] makeKeyAndOrderFront:sender];
}

- (IBAction)toggleHistory:(id)sender{
    NSDrawerState state = [HistoryDrawer state];
    if (NSDrawerOpeningState == state || NSDrawerOpenState == state) {
        [HistoryDrawer close];
    } else {
        [HistoryDrawer open];
    }
}

-(IBAction) showVignettes:(id)sender {
    if ([VignettesController sharedController])
        [[VignettesController sharedController] showVigenttes];
}

- (IBAction)toggleAuthentication:(id)sender{
    BOOL isOn = [self getRootFlag];
    
    if (isOn) {
        removeRootAuthorization();
        [self setRootFlag:NO];
    } else {
        if (requestRootAuthorization(1)) return;
        [self setRootFlag:YES];
    }
}

- (IBAction)newDocument:(id)sender{
    [[NSDocumentController sharedDocumentController] newDocument: sender];
}

- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename {
    SLog(@" - application:openFile:%@ called", (NSString *)filename);
    NSString *dirname = @"";
    NSString *fname = nil;
    NSString *cmd;
    BOOL isDir;
    BOOL flag = [Preferences flagForKey:enforceInitialWorkingDirectoryKey withDefault:NO];
    NSFileManager *manager = [NSFileManager defaultManager];
    if ([manager fileExistsAtPath:filename isDirectory:&isDir] && isDir){
        [manager changeCurrentDirectoryPath:[filename stringByExpandingTildeInPath]];           
        if (!flag && !appLaunched) {
//          [manager changeCurrentDirectoryPath:[filename stringByExpandingTildeInPath]];           
//          [[REngine mainEngine] executeString:@"sys.load.image('.RData', FALSE)"];
            if ([manager fileExistsAtPath:@".Rprofile"] && ![[manager currentDirectoryPath] isEqualToString:[@"~" stringByExpandingTildeInPath]])
                [[REngine mainEngine] executeString:@"source(\".Rprofile\")"];
            if ([manager fileExistsAtPath:[filename stringByAppendingString:@"/.RData"]]) {
                fname = [[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingString: @"/.RData"] stringByExpandingTildeInPath];
                cmd = [[[NSString alloc] initWithString: @"load(\""] stringByAppendingString: fname];
                cmd = [cmd stringByAppendingString: @"\")"]; 
                [[REngine mainEngine] executeString:cmd];
                NSString *msg = [[[[NSString alloc] initWithString: NLS(@"[Workspace restored from ")] stringByAppendingString: fname] stringByAppendingString: @"]\n\n"];      
                [self handleWriteConsole: msg];
            }
            [self showWorkingDir:nil];
            [self doClearHistory:nil];
            [self doLoadHistory:nil];
        } else {
            [self sendInput:[NSString stringWithFormat:@"setwd(\"%@\")",[filename stringByExpandingTildeInPath]]];
            if ([manager fileExistsAtPath:[filename stringByAppendingString:@"/.RData"]]) {
                fname = [[[[NSFileManager defaultManager] currentDirectoryPath] stringByAppendingString: @"/.RData"] stringByExpandingTildeInPath];
                cmd = [[[NSString alloc] initWithString: @"load(\""] stringByAppendingString: fname];
                cmd = [cmd stringByAppendingString: @"\")"]; 
                [[REngine mainEngine] executeString:cmd];
                NSString *msg = [[[[NSString alloc] initWithString: NLS(@"[Workspace restored from ")] stringByAppendingString: fname] stringByAppendingString: @"]\n\n"];      
                [self handleWriteConsole: msg];
            }
        }
    } else {
        if (!flag && !appLaunched) {
            int i, j=-1;
            for (i=[filename length]-1 ; i>=0 ; i--) {
                if ([filename characterAtIndex:i] == '/') {
                    j = i; i = -1;
                };
            }
            dirname = [filename substringWithRange: NSMakeRange(0, j+1)];
            [manager changeCurrentDirectoryPath:[dirname stringByExpandingTildeInPath]];
            [self showWorkingDir:nil];                  
            [self doClearHistory:nil];
            [self doLoadHistory:nil];
        }
        BOOL openInEditor = [Preferences flagForKey:editOrSourceKey withDefault: YES];
        if (openInEditor || appLaunched) {
            NSURL *url = [[NSURL alloc] initFileURLWithPath:filename];
            NSError *theError;
            SLog(@" - application:openFile path of URL: <%@>", [url absoluteString]);
            [[NSDocumentController sharedDocumentController] openDocumentWithContentsOfURL:url display:YES error:&theError];
        } else {
            int res = [[RController sharedController] isImageData: filename];
            SLog(@"RDocumentController.openDocumentWithContentsOfFile: %@", filename);
            if (res == 0 ) {
                SLog(@" - detected save image, invoking load instead of the editor");
                [[RController sharedController] sendInput: [NSString stringWithFormat:@"load(\"%@\")", filename]];
            } else 
                [self sendInput:[NSString stringWithFormat:@"source(\"%@\")",[filename stringByExpandingTildeInPath]]];
        }
    }
    SLog(@" - application:openFile:%@ with wd: <%@> done", (NSString *)filename, dirname);

    return YES;
}

- (IBAction)openDocument:(id)sender{
    SLog(@" - openDocument");
    char buf[1000];
    [self handleChooseFile:buf len:1000 isNew:0];
    [self application:NSApp openFile:[NSString stringWithUTF8String:buf]];
//  [[RDocumentController sharedDocumentController] openDocument: sender];
}

- (IBAction)saveDocumentAs:(id)sender{
    NSDocument *cd = [[NSDocumentController sharedDocumentController] currentDocument];
    
    if (cd)
        [cd saveDocumentAs:sender];
    else {
        int answer;
        NSSavePanel *sp;
        sp = [NSSavePanel savePanel];
        [sp setRequiredFileType:@"txt"];
        [sp setTitle:NLS(@"Save R Console To File")];
        answer = [sp runModalForDirectory:nil file:@"R Console.txt"];
        
        if(answer == NSOKButton) {
            [[consoleTextView string] writeToFile:[sp filename] atomically:YES];
        }
    }
    [RConsoleWindow makeKeyWindow];
}

- (IBAction)saveDocument:(id)sender{
    NSDocument *cd = [[NSDocumentController sharedDocumentController] currentDocument];
    
    if (cd)
        [cd saveDocument:sender];
    else // for the console this is the same as Save As ..
        [self saveDocumentAs:sender];
    [RConsoleWindow makeKeyWindow];
}

- (IBAction)runPageLayout:(id)sender {
    [NSApp runPageLayout:sender];
}

- (int) handleChooseFile:(char *)buf len:(int)len isNew:(int)isNew
{
    const char *fn;
    int answer;
    NSSavePanel *sp;
    NSOpenPanel *op;
    
    *buf = 0;
    if(isNew==1){
        sp = [NSSavePanel savePanel];
        [sp setTitle:NLS(@"Choose New File Name")];
        answer = [sp runModalForDirectory:nil file:nil];
        
        if(answer == NSOKButton) {
            if([sp filename] != nil){
                fn = [[sp filename] UTF8String];
                if (strlen(fn)>=len) {
                    SLog(@"** handleChooseFile: bufer too small, truncating");
                    memcpy(buf, fn, len-1);
                    buf[len-1]=0;
                } else
                    strcpy(buf, fn);
            }
        }
    } else {
        op = [NSOpenPanel openPanel];
        [op setTitle:NLS(@"Choose File")];
        answer = [op runModalForDirectory:nil file:nil];
        
        if(answer == NSOKButton) {
            if([op filename] != nil){
                fn = [[op filename] UTF8String];
                if (strlen(fn)>=len) {
                    SLog(@"** handleChooseFile: bufer too small, truncating");
                    memcpy(buf, fn, len-1);
                    buf[len-1]=0;
                } else
                    strcpy(buf, fn);
            }
        }
    }
    [RConsoleWindow makeKeyWindow];
    return strlen(buf); // is is used? it's potentially incorrect...
}

- (void) loadFile:(NSString *)fname
{
    int res = [[RController sharedController] isImageData:fname];
    
    switch(res){
        case -1:
            NSLog(@"cannot open file");
            break;
            
        case 0:
            [self sendInput: [NSString stringWithFormat:@"load(\"%@\")",fname]];
            break;
            
        case 1:
            [self sendInput: [NSString stringWithFormat:@"source(\"%@\")",fname]];
            break;  
        default:
            break; 
    }
}

// FIXME: is this really sufficient? what about compressed files?
/*  isImageData:    returns -1 on error, 0 if the file is RDX2 or RDX1, 
1 otherwise.
*/  
- (int)isImageData:(NSString *)fname
{
    NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:fname];
    NSData *header;
    unsigned char buf[5];

    if (!fh)
        return -1;

    header = [fh readDataOfLength:4];
    [fh closeFile];
    
    if (!header || [header length]<4)
        return 1; /* if it's less that 4 bytes then it can't be RData */

    memcpy((char*)buf, [header bytes], 4);
    
    buf[4]=0;
    if( (strcmp((char*)buf,"RDX2")==0) || ((strcmp((char*)buf,"RDX1")==0)) ||
        (buf[0]==0x1f && buf[1]==0x8b)) /* or gzip signature - packed RData */
        return(0);
    return(1);
}

- (void) doProcessEvents: (BOOL) blocking {
    NSEvent *event;
    
    // avoid re-entrant event processing
    if (processingEvents) return;
    
    processingEvents = YES;
    @try {
#ifdef USE_POOLS
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
#endif
        if (blocking){
            event = [NSApp nextEventMatchingMask:NSAnyEventMask
                                       untilDate:[NSDate distantFuture]
                                          inMode:NSDefaultRunLoopMode
                                         dequeue:YES];
            [NSApp sendEvent:event];    
        } else {
            while( (event = [NSApp nextEventMatchingMask:NSAnyEventMask
                                               untilDate:[NSDate dateWithTimeIntervalSinceNow:0.0001]
                                                  inMode:NSDefaultRunLoopMode 
                                                 dequeue:YES]))
                [NSApp sendEvent:event];
        }
#ifdef USE_POOLS
        [pool release];
#endif
    }
    @catch (NSException *foo) {
        NSLog(@"*** RController: caught ObjC exception while processing system events. Update to the latest GUI version and consider reporting this properly (see FAQ) if it persists and is not known. \n*** reason: %@\n*** name: %@, info: %@\n*** Version: R %s.%s (%s) R.app %@%s\nConsider saving your work soon in case this develops into a problem.", [foo reason], [foo name], [foo userInfo], R_MAJOR, R_MINOR, R_SVN_REVISION, [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"], getenv("R_ARCH"));
    }
    processingEvents = NO;
    if (breakPending) {
        breakPending = NO;
        onintr();
    }
    return;
}

- (void) handleProcessEvents{
    [self doProcessEvents: NO];
}


/* 
This method calls the showHelpFor method of the Help Manager which opens
 the internal html browser/help system of R.app
 This method is called from ReadConsole.
 
 The input C string 'topic' is parsed and the behaviour is the following:
 
 topic = ?something  => showHelpFor:@"something"
 topic = help(something) => showHelpFor:@"something"
 topic = help(something); print(anotherthing);   =>  showHelpFor:@"something"
 
 which means that all the rest of the input is discarded.
 No error message or warning are raised.
 */

- (void) openHelpFor: (char *) topic 
{
    char tmp[300];
    int i;
    
//  NSLog(@"openHelpFor: <%@>", [NSString stringWithCString:topic]);
    if(topic[0] == '?' && (strlen(topic)>2))
        [[HelpManager sharedController] showHelpFor: 
            [NSString stringWithUTF8String:topic+1]];
    if(strncmp("help(",topic,5)==0){
        for(i=5;i<strlen(topic); i++){
            if(topic[i]==')')
                break;
            tmp[i-5] = topic[i];
        }
        tmp[i-5] = '\0';
        [[HelpManager sharedController] showHelpFor: [NSString stringWithUTF8String:tmp]];
    }
}

- (void) setupToolbar {
    
    // Create a new toolbar instance, and attach it to our document window 
    toolbar = [[[NSToolbar alloc] initWithIdentifier: RToolbarIdentifier] autorelease];
    
    // Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults 
    [toolbar setAllowsUserCustomization: YES];
    [toolbar setAutosavesConfiguration: YES];
    [toolbar setDisplayMode: NSToolbarDisplayModeIconOnly];
    
    // We are the delegate
    [toolbar setDelegate: self];
    
    // Attach the toolbar to the document window 
    [RConsoleWindow setToolbar: toolbar];
}

- (NSToolbarItem *) toolbar: (NSToolbar *)toolbar itemForItemIdentifier: (NSString *) itemIdent willBeInsertedIntoToolbar:(BOOL) willBeInserted {
    // Required delegate method:  Given an item identifier, this method returns an item 
    // The toolbar will use this method to obtain toolbar items that can be displayed in the customization sheet, or in the toolbar itself 
    NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdent] autorelease];
    
    if ([itemIdent isEqual: SaveDocToolbarItemIdentifier]) {
        // Set the text label to be displayed in the toolbar and customization palette 
        [toolbarItem setLabel: NLS(@"Save")];
        [toolbarItem setPaletteLabel: NLS(@"Save Console Window")];
        [toolbarItem setToolTip: NLS(@"Save R console window")];
        [toolbarItem setImage: [NSImage imageNamed: @"SaveDocumentItemImage"]];
        [toolbarItem setTarget: self];
        [toolbarItem setAction: @selector(saveDocument:)];
    } else if ([itemIdent isEqual: NewEditWinToolbarItemIdentifier]) {
        // Set the text label to be displayed in the toolbar and customization palette 
        [toolbarItem setLabel: NLS(@"New Document")];
        [toolbarItem setPaletteLabel: NLS(@"New Document")];
        [toolbarItem setToolTip: NLS(@"Create a new, empty document in the editor")];
        [toolbarItem setImage: [NSImage imageNamed: @"emptyDoc"]];
        [toolbarItem setTarget: self];
        [toolbarItem setAction: @selector(newDocument:)];
        
    } else  if ([itemIdent isEqual: X11ToolbarItemIdentifier]) {
        [toolbarItem setLabel: NLS(@"Start X11")];
        [toolbarItem setPaletteLabel: NLS(@"Start X11 Server")];
        [toolbarItem setToolTip: NLS(@"Start the X11 window server to allow R using X11 device and Tcl/Tk")];
        [toolbarItem setImage: [NSImage imageNamed: @"X11"]];
        [toolbarItem setTarget: self];
        [toolbarItem setAction: @selector(runX11:)];
        
    }  else  if ([itemIdent isEqual: SetColorsToolbarItemIdentifier]) {
        [toolbarItem setLabel: NLS(@"Set Colors")];
        [toolbarItem setPaletteLabel: NLS(@"Set R Colors")];
        [toolbarItem setToolTip: NLS(@"Set R console colors")];
        [toolbarItem setImage: [NSImage imageNamed: @"colors"]];
        [toolbarItem setTarget: self];
        [toolbarItem setAction: @selector(openColors:)];
        
    } else  if ([itemIdent isEqual: LoadFileInEditorToolbarItemIdentifier]) {
        [toolbarItem setLabel: NLS(@"Open In Editor")];
        [toolbarItem setPaletteLabel: NLS(@"Open In Editor")];
        [toolbarItem setToolTip: NLS(@"Open document in editor")];
        [toolbarItem setImage: [NSImage imageNamed: @"Rdoc"]];
        [toolbarItem setTarget: self];
        [toolbarItem setAction: @selector(openDocument:)];
        
    } else  if ([itemIdent isEqual: SourceRCodeToolbarIdentifier]) {
        [toolbarItem setLabel: NLS(@"Source/Load")];
        [toolbarItem setPaletteLabel: NLS(@"Source or Load in R")];
        [toolbarItem setToolTip: NLS(@"Source script or load data in R")];
        [toolbarItem setImage: [NSImage imageNamed: @"sourceR"]];
        [toolbarItem setTarget: self];
        [toolbarItem setAction: @selector(sourceOrLoadFile:)];
        
    } else if([itemIdent isEqual: NewQuartzToolbarItemIdentifier]) {
        [toolbarItem setLabel: NLS(@"Quartz")];
        [toolbarItem setPaletteLabel: NLS(@"Quartz")];
        [toolbarItem setToolTip: NLS(@"Open a new Quartz device window")];
        [toolbarItem setImage: [NSImage imageNamed: @"quartz"]];
        [toolbarItem setTarget: self];
        [toolbarItem setAction: @selector(newQuartzDevice:) ];
        
    } else if([itemIdent isEqual: InterruptToolbarItemIdentifier]) {
        [toolbarItem setLabel: NLS(@"Stop")];
        [toolbarItem setPaletteLabel: NLS(@"Stop")];
        toolbarStopItem = toolbarItem;
        [toolbarItem setToolTip: NLS(@"Interrupt current R computation")];
        [toolbarItem setImage: [NSImage imageNamed: @"stop"]];
        [toolbarItem setTarget: self];
        [toolbarItem setAction: @selector(breakR:) ];
        
    }  else if([itemIdent isEqual: FontSizeToolbarItemIdentifier]) {
        [toolbarItem setLabel: NLS(@"Font Size")];
        [toolbarItem setPaletteLabel: NLS(@"Font Size")];
        [toolbarItem setToolTip: NLS(@"Change the size of R console font")];
        [toolbarItem setTarget: self];
        [toolbarItem performSelector:@selector(setView:) withObject:fontSizeView];
        [toolbarItem setAction:NULL];
        [toolbarItem setView:fontSizeView];
        if ([toolbarItem view]!=NULL)
        {
            [toolbarItem setMinSize:[[toolbarItem view] bounds].size];
            [toolbarItem setMaxSize:[[toolbarItem view] bounds].size];
        }
        
    }  else if([itemIdent isEqual: NewQuartzToolbarItemIdentifier]) {
        [toolbarItem setLabel: NLS(@"Quartz")];
        [toolbarItem setPaletteLabel: NLS(@"Quartz")];
        [toolbarItem setToolTip: NLS(@"Open a new Quartz device window")];
        [toolbarItem setImage: [NSImage imageNamed: @"quartz"]];
        [toolbarItem setTarget: self];
        [toolbarItem setAction: @selector(newQuartzDevice:) ];
        
    } else if([itemIdent isEqual: ShowHistoryToolbarItemIdentifier]) {
        [toolbarItem setLabel: NLS(@"History")];
        [toolbarItem setPaletteLabel: NLS(@"History")];
        [toolbarItem setToolTip: NLS(@"Show/Hide R command history")];
        [toolbarItem setImage: [NSImage imageNamed: @"history"]];
        [toolbarItem setTarget: self];
        [toolbarItem setAction: @selector(toggleHistory:) ];            
    } else if([itemIdent isEqual: AuthenticationToolbarItemIdentifier]) {
        [toolbarItem setLabel: NLS(@"Authentication")];
        [toolbarItem setPaletteLabel: NLS(@"Authentication")];
        [toolbarItem setToolTip: NLS(@"Authorize R to run system commands as root")];
        [toolbarItem setImage: [NSImage imageNamed: @"lock-locked"]];
        [toolbarItem setTarget: self];
        [toolbarItem setAction: @selector(toggleAuthentication:) ];
        
    } else if([itemIdent isEqual: QuitRToolbarItemIdentifier]) {
        [toolbarItem setLabel: NLS(@"Quit")];
        [toolbarItem setPaletteLabel: NLS(@"Quit")];
        [toolbarItem setToolTip: NLS(@"Quit R")];
        [toolbarItem setImage: [NSImage imageNamed: @"quit"]];
        [toolbarItem setTarget: self];
        [toolbarItem setAction: @selector(quitR:) ];
        
    } else {
        // itemIdent refered to a toolbar item that is not provide or supported by us or cocoa 
        // Returning nil will inform the toolbar this kind of item is not supported 
        toolbarItem = nil;
    }
    return toolbarItem;
}


- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar {
    // Required delegate method:  Returns the ordered list of items to be shown in the toolbar by default    
    // If during the toolbar's initialization, no overriding values are found in the user defaults, or if the
    // user chooses to revert to the default items this set will be used 
    return [NSArray arrayWithObjects:   InterruptToolbarItemIdentifier, SourceRCodeToolbarIdentifier,
        NewQuartzToolbarItemIdentifier, X11ToolbarItemIdentifier,
        NSToolbarSeparatorItemIdentifier,
        AuthenticationToolbarItemIdentifier, ShowHistoryToolbarItemIdentifier,
        SetColorsToolbarItemIdentifier,
        NSToolbarSeparatorItemIdentifier, /* SaveDocToolbarItemIdentifier, */
        LoadFileInEditorToolbarItemIdentifier,
        NewEditWinToolbarItemIdentifier, NSToolbarPrintItemIdentifier, 
        NSToolbarSeparatorItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier,
        QuitRToolbarItemIdentifier, nil];
}

- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar {
    // Required delegate method:  Returns the list of all allowed items by identifier.  By default, the toolbar 
    // does not assume any items are allowed, even the separator.  So, every allowed item must be explicitly listed   
    // The set of allowed items is used to construct the customization palette 
    return [NSArray arrayWithObjects:   QuitRToolbarItemIdentifier, AuthenticationToolbarItemIdentifier, ShowHistoryToolbarItemIdentifier, 
        InterruptToolbarItemIdentifier, NewQuartzToolbarItemIdentifier, /* SaveDocToolbarItemIdentifier, */
        NewEditWinToolbarItemIdentifier, LoadFileInEditorToolbarItemIdentifier,
        NSToolbarPrintItemIdentifier, NSToolbarCustomizeToolbarItemIdentifier,
        NSToolbarFlexibleSpaceItemIdentifier, NSToolbarSpaceItemIdentifier, 
        NSToolbarSeparatorItemIdentifier, X11ToolbarItemIdentifier,
        SetColorsToolbarItemIdentifier,
        FontSizeToolbarItemIdentifier, SourceRCodeToolbarIdentifier, nil];
}

- (void) toolbarWillAddItem: (NSNotification *) notif {
    // Optional delegate method:  Before an new item is added to the toolbar, this notification is posted.
    // This is the best place to notice a new item is going into the toolbar.  For instance, if you need to 
    // cache a reference to the toolbar item or need to set up some initial state, this is the best place 
    // to do it.  The notification object is the toolbar to which the item is being added.  The item being 
    // added is found by referencing the @"item" key in the userInfo 
    NSToolbarItem *addedItem = [[notif userInfo] objectForKey: @"item"];
    if ([[addedItem itemIdentifier] isEqual: NSToolbarPrintItemIdentifier]) {
        [addedItem setToolTip: NLS(@"Print this document")];
        [addedItem setTarget: self];
    }
}  

- (void) toolbarDidRemoveItem: (NSNotification *) notif {
    // Optional delegate method:  After an item is removed from a toolbar, this notification is sent.   This allows 
    // the chance to tear down information related to the item that may have been cached.   The notification object
    // is the toolbar from which the item is being removed.  The item being added is found by referencing the @"item"
    // key in the userInfo 
    // NSToolbarItem *removedItem = [[notif userInfo] objectForKey: @"item"];
}

- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem {
    // Optional method:  This message is sent to us since we are the target of some toolbar item actions 
    // (for example:  of the save items action) 
    BOOL enable = NO;
    if ([[toolbarItem itemIdentifier] isEqual: SaveDocToolbarItemIdentifier]) {
        enable = [RConsoleWindow isDocumentEdited];
    } else if ([[toolbarItem itemIdentifier] isEqual: SourceRCodeToolbarIdentifier]) {
        enable = YES;
    } else if ([[toolbarItem itemIdentifier] isEqual: X11ToolbarItemIdentifier]) {
        enable = YES;
    } else if ([[toolbarItem itemIdentifier] isEqual: SetColorsToolbarItemIdentifier]) {
        enable = YES;
    } else if ([[toolbarItem itemIdentifier] isEqual: LoadFileInEditorToolbarItemIdentifier]) {
        enable = YES;
    } else if ([[toolbarItem itemIdentifier] isEqual: NewEditWinToolbarItemIdentifier]) {
        enable = YES;
    } else if ([[toolbarItem itemIdentifier] isEqual: NSToolbarPrintItemIdentifier]) {
        enable = YES;
    } else if ([[toolbarItem itemIdentifier] isEqual: NewQuartzToolbarItemIdentifier]) {
        enable = YES;
    } else if ([[toolbarItem itemIdentifier] isEqual: InterruptToolbarItemIdentifier]) {
        enable = (busyRFlag || (childPID>0));
    } else if ([[toolbarItem itemIdentifier] isEqual: ShowHistoryToolbarItemIdentifier]) {
        enable = YES;
    } else if ([[toolbarItem itemIdentifier] isEqual: AuthenticationToolbarItemIdentifier]) {
        enable = YES;
    } else if ([[toolbarItem itemIdentifier] isEqual: QuitRToolbarItemIdentifier]) {
        enable = YES;
    }       
    return enable;
}


/* This is needed to force the NSDocument to know when edited windows are dirty */
- (void) RConsoleDidResize: (NSNotification *)notification{
    [self setOptionWidth:NO];
}

- (void) setOptionWidth:(BOOL)force;
{
    float newSize = [[consoleTextView textContainer] containerSize].width;
    float newFontSize = [[consoleTextView font] pointSize];
    if((newSize != currentSize) | (newFontSize != currentFontSize) | force){
        float newConsoleWidth = 1.5*newSize/newFontSize-1.0;
        if((int)newConsoleWidth != (int)currentConsoleWidth){
            R_SetOptionWidth(newConsoleWidth);
            currentSize = newSize;  
            currentFontSize = newFontSize;
            currentConsoleWidth = newConsoleWidth;
        }
    }
}

-(IBAction) checkForUpdates:(id)sender{
    [[REngine mainEngine] executeString: @"Rapp.updates()"];
}

-(IBAction) getWorkingDir:(id)sender
{
    [self sendInput:@"getwd()"];
    //  [[REngine mainEngine] evaluateString: @"getwd()"];
    
}

-(IBAction) resetWorkingDir:(id)sender
{
    [[NSFileManager defaultManager] changeCurrentDirectoryPath: [[Preferences stringForKey:@"initialWorkingDirectoryKey" withDefault:@"~"] stringByExpandingTildeInPath]];
    [self showWorkingDir:sender];
}

-(IBAction) setWorkingDir:(id)sender
{
    NSOpenPanel *op;
    int answer;

    op = [NSOpenPanel openPanel];
    [op setCanChooseDirectories:YES];
    [op setCanChooseFiles:NO];
    [op setTitle:NLS(@"Choose New Working Directory")];
    
    answer = [op runModalForDirectory:[[NSFileManager defaultManager] currentDirectoryPath] file:nil types:[NSArray arrayWithObject:@""]];
    
    if(answer == NSOKButton && [op directory] != nil)
        [[NSFileManager defaultManager] changeCurrentDirectoryPath:[[op directory] stringByExpandingTildeInPath]];
    [self showWorkingDir:sender];
    [RConsoleWindow makeKeyWindow];
}

- (IBAction) showWorkingDir:(id)sender
{
    NSString *wd = [[NSFileManager defaultManager] currentDirectoryPath];
    if (!wd) wd = NLS(@"<deleted>");
    if (!lastShownWD || ![wd isEqual:lastShownWD]) {
        if (lastShownWD) [lastShownWD release];
        lastShownWD = [wd retain];
        [WDirView setEditable:YES];
        [WDirView setStringValue: [wd stringByAbbreviatingWithTildeInPath]];
        [WDirView setEditable:NO];
    }
}


/**** NOTE: the following install...: methods are no longer used by the PakcageInstaller because they are wrong!
 ****       (Incorrect target directory and packages type) Correct implementations are inside the PI.
 ****       Remove as soon as it is clear that no NIB code uses it either! */
- (IBAction)installFromDir:(id)sender
{
    NSOpenPanel *op;
    int answer;
    
    op = [NSOpenPanel openPanel];
    [op setCanChooseDirectories:YES];
    [op setCanChooseFiles:NO];
    [op setTitle:NLS(@"Select Package Directory")];
    
    answer = [op runModalForDirectory:nil file:nil types:[NSArray arrayWithObject:@""]];
    [op setCanChooseDirectories:NO];
    [op setCanChooseFiles:YES];     
    
    if(answer == NSOKButton) 
        if([op directory] != nil)
            [[REngine mainEngine] executeString: [NSString stringWithFormat:@"install.packages(\"%@\",,NULL,type='source')",[op directory]] ];
}

- (IBAction)installFromBinary:(id)sender
{
    [[REngine mainEngine] executeString: @"install.packages(file.choose(),,NULL,type='mac.binary')" ];
}

- (IBAction)installFromSource:(id)sender
{
    [[REngine mainEngine] executeString: @"install.packages(file.choose(),,NULL,type='source')" ];
}
/**** end of obsolete API ****/

- (IBAction)togglePackageInstaller:(id)sender
{
    [[PackageInstaller sharedController] show];
}

- (IBAction)toggleWSBrowser:(id)sender
{
    [WSBrowser toggleWorkspaceBrowser];
    [[REngine mainEngine] executeString:@"browseEnv(html=F)"];  
    [RConsoleWindow makeKeyWindow];
}

- (IBAction)loadWorkSpace:(id)sender
{
    [self sendInput:@"load(\".RData\")"];
    //  [[REngine mainEngine] evaluateString:@"load(\".RData\")" ]; 
    [RConsoleWindow makeKeyWindow];
}

- (IBAction)saveWorkSpace:(id)sender
{
    [self sendInput:@"save.image()"];
    //  [[REngine mainEngine] evaluateString:@"save.image()"];
    [RConsoleWindow makeKeyWindow];
}

- (IBAction)loadWorkSpaceFile:(id)sender
{
    [[REngine mainEngine] executeString:@"load(file.choose())"];
    [RConsoleWindow makeKeyWindow];
}                   

- (IBAction)saveWorkSpaceFile:(id)sender
{
    [[REngine mainEngine] executeString: @"save.image(file=file.choose(TRUE))"];
    [RConsoleWindow makeKeyWindow];
}

- (IBAction)showWorkSpace:(id)sender{
    [self sendInput:@"ls()"];
    [RConsoleWindow makeKeyWindow];
}

- (IBAction)clearWorkSpace:(id)sender
{
    NSBeginAlertSheet(NLS(@"Clear Workspace"), NLS(@"Yes"), NLS(@"No") , nil, RConsoleWindow, self, @selector(shouldClearWS:returnCode:contextInfo:), NULL, NULL,
                      NLS(@"All objects in the workspace will be removed. Are you sure you want to proceed?"));
    [RConsoleWindow makeKeyWindow];
}

/* this gets called by the "wanna save?" sheet on window close */
- (void) shouldClearWS:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo {
    if (returnCode==NSAlertDefaultReturn)
        [[REngine mainEngine] executeString: @"rm(list=ls())"];
}

- (IBAction)togglePackageManager:(id)sender
{
    if ([[PackageManager sharedController] count]==0)
        [[REngine mainEngine] executeString:@"package.manager()"];
    else
        [[PackageManager sharedController] show];
}

- (IBAction)toggleDataManager:(id)sender
{
    if ([[DataManager sharedController] count]==0) {
        [[DataManager sharedController] show];
        [[REngine mainEngine] executeString: @"data.manager()"];
    } else
        [[DataManager sharedController] show];
}


-(IBAction) runX11:(id)sender{
    system("open -a X11.app");
}

-(IBAction) openColors:(id)sender{
    [prefsCtrl selectPaneWithIdentifier:@"Colors"];
    [prefsCtrl showWindow:self];
    [[prefsCtrl window] makeKeyAndOrderFront:self];
}

- (IBAction)performHelpSearch:(id)sender {
    if ([[sender stringValue] length]>0) {
        [[HelpManager sharedController] showHelpFor:[sender stringValue]];
        [helpSearch setStringValue:@""];
    }
    [RConsoleWindow makeKeyWindow];
}

- (IBAction)sourceOrLoadFile:(id)sender
{
    int answer;
    NSOpenPanel *op;
    op = [NSOpenPanel openPanel];
    [op setTitle:NLS(@"R File to Source/Load")];
    answer = [op runModalForTypes:nil];
    
    if (answer==NSOKButton)
        [self loadFile:[op filename]];
    [RConsoleWindow makeKeyWindow];
}

- (IBAction)sourceFile:(id)sender
{
    int answer;
    NSOpenPanel *op;
    op = [NSOpenPanel openPanel];
    [op setTitle:NLS(@"R File to Source")];
    answer = [op runModalForTypes:nil];
    
    if (answer==NSOKButton)
        [self sendInput:[NSString stringWithFormat:@"source(\"%@\")",[op filename]]];
    [RConsoleWindow makeKeyWindow];
}

- (IBAction)printDocument:(id)sender
{
    NSPrintInfo *printInfo;
    NSPrintInfo *sharedInfo;
    NSPrintOperation *printOp;
    NSMutableDictionary *printInfoDict;
    NSMutableDictionary *sharedDict;
    
    sharedInfo = [NSPrintInfo sharedPrintInfo];
    sharedDict = [sharedInfo dictionary];
    printInfoDict = [NSMutableDictionary dictionaryWithDictionary:
        sharedDict];
    
    printInfo = [[NSPrintInfo alloc] initWithDictionary: printInfoDict];
    [printInfo setHorizontalPagination: NSAutoPagination];
    [printInfo setVerticalPagination: NSAutoPagination];
    [printInfo setVerticallyCentered:NO];
    
    printOp = [NSPrintOperation printOperationWithView:consoleTextView 
                                             printInfo:printInfo];
    [printOp setShowPanels:YES];
    [printOp runOperation];
}

- (IBAction) setDefaultColors:(id)sender {
    int i = 0, ccs = [consoleColorsKeys count];
    [[Preferences sharedPreferences] beginBatch];
    while (i<ccs) {
        [Preferences setKey:[consoleColorsKeys objectAtIndex:i] withArchivedObject:[defaultConsoleColors objectAtIndex: i]];
        i++;
    }
    [[Preferences sharedPreferences] endBatch];
}

- (void) updatePreferences {
    SLog(@"RController.updatePreferences");
    currentFontSize = [Preferences floatForKey: FontSizeKey withDefault: 11.0];
    NSFont *newFont = [NSFont userFixedPitchFontOfSize:currentFontSize];
    if (newFont!=textFont) {
        SLog(@" - releasing %@", textFont);
        [textFont release];
        SLog(@" - using new %@", textFont);
        textFont = [newFont retain];
        [consoleTextView setFont:textFont];
    }

    argsHints=[Preferences flagForKey:prefShowArgsHints withDefault:YES];
    RTextView_autoCloseBrackets = [Preferences flagForKey:kAutoCloseBrackets withDefault:YES];
    {
        int i = 0, ccs = [consoleColorsKeys count];
        while (i<ccs) {
            NSColor *c = [Preferences unarchivedObjectForKey: [consoleColorsKeys objectAtIndex:i] withDefault: [consoleColors objectAtIndex:i]];
            if (c != [consoleColors objectAtIndex:i]) {
                [consoleColors replaceObjectAtIndex:i withObject:c];
                if (i == iBackgroundColor) {
                    [RConsoleWindow setBackgroundColor:c];
                    [RConsoleWindow display];
                }
            }
            i++;
        }
        [consoleTextView setInsertionPointColor:[consoleColors objectAtIndex:iInputColor]];
    }
    [consoleTextView setNeedsDisplay:YES];
    SLog(@" - done, preferences updated");
}

- (NSWindow*) window {
    return RConsoleWindow;
}

- (NSTextView *)getRTextView{
    return consoleTextView;
}

- (NSWindow *)getRConsoleWindow{
    return RConsoleWindow;
}

- (void) setStatusLineText: (NSString*) text {
    SLog(@"RController.setStatusLine: \"%@\"", text);
    [statusLine setStringValue:text?text:@""];
}

- (NSString*) statusLineText {
    return [statusLine stringValue];
}

- (int) quitRequest: (int) saveFlag withCode: (int) code last: (int) runLast
{
    if (saveFlag == 0)
        requestSaveAction = @"no";
    if (saveFlag == 1)
        requestSaveAction = @"yes";
    [[NSApplication sharedApplication] terminate:self];
    return 1;
}

- (int) helpServerPort {
#if R_VERSION < R_Version(2, 10, 0)
    return 0;
#else
    REngine *re = [REngine mainEngine]; 
    RSEXP *x = [re evaluateString:@"tools:::httpdPort"];
    int port = 0;
    if (x) {
        port = [x integer];
        [x release];
    }
    if (port != 0) return port;
    [re executeString:@"tools::startDynamicHelp()"];
    x = [re evaluateString:@"tools:::httpdPort"];
    if (x) {
        port = [x integer];
        [x release];
    }
    return port;
#endif
}

@end