The R Project SVN R-packages

Rev

Rev 1562 | Rev 1666 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
714 iacus 1
/*
2
 *  R.app : a Cocoa front end to: "R A Computer Language for Statistical Data Analysis"
3
 *  
4
 *  R.app Copyright notes:
1254 iacus 5
 *                     Copyright (C) 2004-5  The R Foundation
714 iacus 6
 *                     written by Stefano M. Iacus and Simon Urbanek
7
 *
8
 *                  
9
 *  R Copyright notes:
10
 *                     Copyright (C) 1995-1996   Robert Gentleman and Ross Ihaka
11
 *                     Copyright (C) 1998-2001   The R Development Core Team
12
 *                     Copyright (C) 2002-2004   The R Foundation
13
 *
14
 *  This program is free software; you can redistribute it and/or modify
15
 *  it under the terms of the GNU General Public License as published by
16
 *  the Free Software Foundation; either version 2 of the License, or
17
 *  (at your option) any later version.
18
 *
19
 *  This program is distributed in the hope that it will be useful,
20
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 *  GNU General Public License for more details.
23
 *
24
 *  A copy of the GNU General Public License is available via WWW at
25
 *  http://www.gnu.org/copyleft/gpl.html.  You can also obtain it by
26
 *  writing to the Free Software Foundation, Inc., 59 Temple Place,
27
 *  Suite 330, Boston, MA  02111-1307  USA.
1362 urbaneks 28
 *
29
 *  $Id: RController.m 1603 2005-06-20 16:50:33Z urbaneks $
714 iacus 30
 */
31
 
1045 urbaneks 32
#import "RGUI.h"
928 urbaneks 33
#include <R.h>
930 urbaneks 34
#include <Rversion.h>
568 iacus 35
#include <Rinternals.h>
36
#include <R_ext/Parse.h>
37
#include <Fileio.h>
930 urbaneks 38
#if (R_VERSION >= R_Version(2,1,0))
39
// Rinterface is public, but present only in 2.1+
929 urbaneks 40
#include <Rinterface.h>
930 urbaneks 41
#else
42
// if there is no Rinterface, we need private Defn.h
43
#include <Defn.h>
44
#endif
1089 urbaneks 45
#include <langinfo.h>
1136 urbaneks 46
#include <locale.h>
1089 urbaneks 47
 
568 iacus 48
#import <sys/fcntl.h>
49
#import <sys/select.h>
50
#import <sys/types.h>
51
#import <sys/time.h>
688 urbaneks 52
#import <sys/wait.h>
568 iacus 53
#import <signal.h>
54
#import <unistd.h>
869 urbaneks 55
#import "RController.h"
816 urbaneks 56
#import "REngine/Rcallbacks.h"
57
#import "REngine/Rengine.h"
898 urbaneks 58
#import "RConsoleTextStorage.h"
1420 urbaneks 59
#import "RDocumentWinCtrl.h"
568 iacus 60
 
816 urbaneks 61
#import "Tools/Authorization.h"
1362 urbaneks 62
 
815 urbaneks 63
#import "Preferences.h"
796 urbaneks 64
#import "SearchTable.h"
638 iacus 65
 
568 iacus 66
// size of the console output cache buffer
67
#define DEFAULT_WRITE_BUFFER_SIZE 32768
68
// 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.
69
#define writeBufferHighWaterMark  (DEFAULT_WRITE_BUFFER_SIZE-4096)
70
// low water-mark of the buffer - if less than the water mark is available then the buffer will be flushed
71
#define writeBufferLowWaterMark   2048
72
 
73
/*  RController.m: main GUI code originally based on Simon Urbanek's work of embedding R in Cocoa app (RGui?)
783 urbaneks 74
The Code and File Completion is due to Simon U.
75
History handler is due to Simon U.
568 iacus 76
*/
77
 
78
typedef struct {
783 urbaneks 79
	ParseStatus    status;
80
	int            prompt_type;
81
	int            browselevel;
82
	unsigned char  buf[1025];
83
	unsigned char *bufp;
568 iacus 84
} R_ReplState;
85
 
86
extern R_ReplState state;
87
 
88
void run_Rmainloop(void); // from Rinit.c
89
extern void RGUI_ReplConsole(SEXP rho, int savestack, int browselevel); // from Rinit.c
90
extern int RGUI_ReplIteration(SEXP rho, int savestack, int browselevel, R_ReplState *state);
91
 
929 urbaneks 92
// from Defn.h
93
 
94
int R_SetOptionWidth(int);
95
 
568 iacus 96
#import "RController.h"
816 urbaneks 97
#import "Tools/CodeCompletion.h"
98
#import "Tools/FileCompletion.h"
1136 urbaneks 99
#import "HelpManager.h"
568 iacus 100
#import "RDocument.h"
101
#import "PackageManager.h"
102
#import "DataManager.h"
103
#import "PackageInstaller.h"
104
#import "WSBrowser.h"
105
#import "HelpManager.h"
816 urbaneks 106
#import "Quartz/RQuartz.h"
710 iacus 107
#import "RDocumentController.h"
1212 iacus 108
#import "SelectList.h"
568 iacus 109
 
110
#import <unistd.h>
111
#import <sys/fcntl.h>
112
 
113
static RController* sharedRController;
114
 
115
@interface NSApplication (ScriptingSupport)
116
- (id)handleDCMDCommand:(NSScriptCommand*)command;
117
@end
118
 
119
@implementation NSApplication (ScriptingSupport)
120
- (id)handleDCMDCommand:(NSScriptCommand*)command
121
{
122
    NSDictionary *args = [command evaluatedArguments];
123
    NSString *cmd = [args objectForKey:@""];
124
    if (!cmd || [cmd isEqualToString:@""])
125
        return [NSNumber numberWithBool:NO];
1490 urbaneks 126
	[[RController sharedController] sendInput: cmd];
568 iacus 127
	return [NSNumber numberWithBool:YES];
128
}
129
@end
130
 
131
@implementation RController
132
 
133
- (id) init {
134
	self = [super init];
135
 
136
	runSystemAsRoot = NO;
137
	toolbar = nil;
138
	toolbarStopItem = nil;
139
	rootFD = -1;
140
	childPID = 0;
141
	RLtimer = nil;
142
	busyRFlag = YES;
1191 urbaneks 143
	appLaunched = NO;
575 urbaneks 144
	outputPosition = promptPosition = committedLength = 0;
568 iacus 145
	consoleInputQueue = [[NSMutableArray alloc] initWithCapacity:8];
146
	currentConsoleInput = nil;
147
	forceStdFlush = NO;
148
	writeBufferLen = DEFAULT_WRITE_BUFFER_SIZE;
149
	writeBufferPos = writeBuffer = (char*) malloc(writeBufferLen);
755 urbaneks 150
	readConsTransBufferSize = 1024; // initial size - will grow as needed
151
	readConsTransBuffer = (char*) malloc(readConsTransBufferSize);
871 urbaneks 152
	textViewSync = [[NSString alloc] initWithString:@"consoleTextViewSemahphore"];
568 iacus 153
 
815 urbaneks 154
	consoleColorsKeys = [[NSArray alloc] initWithObjects:
155
		backgColorKey, inputColorKey, outputColorKey, promptColorKey,
156
		stderrColorKey, stdoutColorKey, rootColorKey];
157
	defaultConsoleColors = [[NSArray alloc] initWithObjects: // default colors
158
		[NSColor whiteColor], [NSColor blueColor], [NSColor blackColor], [NSColor purpleColor],
159
		[NSColor redColor], [NSColor grayColor], [NSColor purpleColor]];
160
	consoleColors = [defaultConsoleColors mutableCopy];
161
 
898 urbaneks 162
	textFont = [[NSFont userFixedPitchFontOfSize:currentFontSize] retain];
568 iacus 163
	return self;
164
}
165
 
166
- (void) setRootFlag: (BOOL) flag
167
{
168
	if (!flag) removeRootAuthorization();
169
	runSystemAsRoot=flag;
170
 
171
	{
172
		NSArray * ia = [toolbar items];
173
		int l = [ia count], i=0;
174
		while (i<l) {
175
			NSToolbarItem *ti = [ia objectAtIndex:i];
176
			if ([[ti itemIdentifier] isEqual:AuthenticationToolbarItemIdentifier]) {
177
				[ti setImage: [NSImage imageNamed: flag?@"lock-unlocked":@"lock-locked"]];
178
				break;
179
			}
180
			i++;
181
		}
182
	}
183
}
184
 
185
- (BOOL) getRootFlag { return runSystemAsRoot; }
186
 
1328 urbaneks 187
- (void) setRootFD: (int) fd {
188
	rootFD=fd;
189
}
190
 
568 iacus 191
- (NSFont*) currentFont
192
{
898 urbaneks 193
	return textFont;
568 iacus 194
}
195
 
196
- (void) awakeFromNib {
1364 urbaneks 197
	char *args[4]={ "R", "--no-save", "--gui=cocoa", 0 };
1328 urbaneks 198
	SLog(@"RController.awakeFromNib");
1362 urbaneks 199
 
568 iacus 200
	sharedRController = self;
747 urbaneks 201
 
898 urbaneks 202
	NSLayoutManager *lm = [[RTextView layoutManager] retain];
203
	NSTextStorage *origTS = [[RTextView textStorage] retain];
204
	RConsoleTextStorage * textStorage = [[RConsoleTextStorage alloc] init];
205
	[origTS removeLayoutManager:lm];
206
	[textStorage addLayoutManager:lm];
207
	[lm release];
208
	[origTS release];
209
 
815 urbaneks 210
	[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
1328 urbaneks 211
	[RConsoleWindow setOpaque:NO]; // Needed so we can see through it when we have clear stuff on top
212
	[RTextView setDrawsBackground:NO];
213
	[[RTextView enclosingScrollView] setDrawsBackground:NO];
815 urbaneks 214
 
1328 urbaneks 215
	SLog(@" - load preferences");
1459 urbaneks 216
	[self updatePreferences]; // first update, then add self
815 urbaneks 217
	[[Preferences sharedPreferences] addDependent: self];
218
 
1328 urbaneks 219
	SLog(@" - init R_LIBS");	
747 urbaneks 220
	{ // first initialize R_LIBS if necessary
815 urbaneks 221
		NSString *prefStr = [Preferences stringForKey:miscRAquaLibPathKey withDefault:nil];
747 urbaneks 222
		BOOL flag = !isAdmin(); // the default is YES for users and NO for admins
815 urbaneks 223
		if (prefStr)
224
			flag=[prefStr isEqualToString: @"YES"];
747 urbaneks 225
		if (flag) {
226
			char *cRLIBS = getenv("R_LIBS");
749 urbaneks 227
			NSString *addPath = [@"~/Library/R/library" stringByExpandingTildeInPath];
1460 urbaneks 228
			if (![[NSFileManager defaultManager] fileExistsAtPath:addPath]) { // make sure the directory exists
229
				[[NSFileManager defaultManager] createDirectoryAtPath:[@"~/Library/R" stringByExpandingTildeInPath] attributes:nil];
230
				[[NSFileManager defaultManager] createDirectoryAtPath:addPath attributes:nil];
231
			}
747 urbaneks 232
			if (cRLIBS && *cRLIBS)
233
				addPath = [NSString stringWithFormat: @"%s:%@", cRLIBS, addPath];
815 urbaneks 234
			setenv("R_LIBS", [addPath UTF8String], 1);
1328 urbaneks 235
			SLog(@" - setting R_LIBS=%s", [addPath UTF8String]);
747 urbaneks 236
		}
815 urbaneks 237
	}
1328 urbaneks 238
	SLog(@" - set APP VERSION");
749 urbaneks 239
	setenv("R_GUI_APP_VERSION", R_GUI_VERSION_STR, 1);
747 urbaneks 240
 
1328 urbaneks 241
	SLog(@" - set R home");
242
	if (!getenv("R_HOME")) {
243
		NSBundle *rfb = [NSBundle bundleWithIdentifier:@"org.r-project.R-framework"];
1372 urbaneks 244
		if (!rfb) {
1328 urbaneks 245
			SLog(@" * problem: R_HOME is not set and I can't find the framework bundle");
1372 urbaneks 246
			if ([[NSFileManager defaultManager] fileExistsAtPath:@"/Library/Frameworks/R.framework/Resources/bin/R"]) {
247
				SLog(@" * I'm being desperate and I found R at /Library/Frameworks/R.framework - so I'll use it, wish me luck");
248
				setenv("R_HOME", "/Library/Frameworks/R.framework/Resources", 1);
249
			} else
250
				SLog(@" * I didn't even found R framework in the default location, I'm giving up - you're on your own");
251
		} else {
1328 urbaneks 252
			SLog(@"   %s", [[rfb resourcePath] UTF8String]);
253
			setenv("R_HOME", [[rfb resourcePath] UTF8String], 1);
254
		}
255
	}
1477 urbaneks 256
 
257
#if (R_VERSION >= R_Version(2,2,0))
258
	 if (!getenv("RGUI_NOWARN_RDEVEL"))
259
	 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);
260
	 {
261
		 char tp[1024];
262
		 if (!getenv("R_INCLUDE_DIR")) {
263
			 strcpy(tp, getenv("R_HOME")); strcat(tp, "/include"); setenv("R_INCLUDE_DIR", tp, 1);
264
		 }
265
		 if (!getenv("R_SHARE_DIR")) {
266
			 strcpy(tp, getenv("R_HOME")); strcat(tp, "/share"); setenv("R_SHARE_DIR", tp, 1);
267
		 }
268
		 if (!getenv("R_DOC_DIR")) {
269
			 strcpy(tp, getenv("R_HOME")); strcat(tp, "/doc"); setenv("R_DOC_DIR", tp, 1);
270
		 }
271
	 }
272
#endif
1328 urbaneks 273
 
274
	/* setup LANG variable to match the system locale based on user's CFLocale */
908 urbaneks 275
#if (R_VERSION >= R_Version(2,1,0))
1328 urbaneks 276
	SLog(@" - set locale");
1433 urbaneks 277
	if ([Preferences stringForKey:@"force.LANG"]) {
278
		const char *ls = [[Preferences stringForKey:@"force.LANG"] UTF8String];
279
		if (*ls) {
280
			setenv("LANG", ls, 1);
281
			SLog(@" - force.LANG present, setting LANG to \"%s\"", ls);
282
		} else
283
			SLog(@" - force.LANG present, but empty. LANG won't be set at all.");
284
	} else if ([Preferences flagForKey:@"ignore.system.locale"]==YES) {
285
		setenv("LANG", "en_US.UTF-8", 1);
286
		SLog(@" - ignore.system.locale is set to YES, using en_US.UTF-8");
287
	} else {
1089 urbaneks 288
		char cloc[64];
289
		char *c = getenv("LANG");
290
		cloc[63]=0;
291
		if (c)
292
			strcpy(cloc, c);
293
		else {
294
			CFLocaleRef lr = CFLocaleCopyCurrent();
295
			CFStringRef ls = CFLocaleGetIdentifier(lr);
296
			strncpy(cloc, [((NSString*)ls) UTF8String],63);
297
			CFRelease(lr);
1048 urbaneks 298
		}
1089 urbaneks 299
 
300
		if (!strchr(cloc,'.'))
301
			strcat(cloc,".UTF-8");
302
		setenv("LANG", cloc, 1);
1328 urbaneks 303
		SLog(@" - setting LANG=%s", getenv("LANG"));
908 urbaneks 304
	}
305
#endif
1499 urbaneks 306
 
307
	BOOL noReenter = [Preferences flagForKey:@"REngine prevent reentrance"];
308
	if (noReenter == YES) preventReentrance = YES;
908 urbaneks 309
 
1328 urbaneks 310
	SLog(@" - init R");
688 urbaneks 311
	[[[REngine alloc] initWithHandler:self arguments:args] setCocoaHandler:self];
1328 urbaneks 312
 
313
	SLog(@" - font and other widgets");
898 urbaneks 314
	/*
568 iacus 315
	[RTextView setFont:[NSFont userFixedPitchFontOfSize:currentFontSize]];
316
	[fontSizeStepper setIntValue:currentFontSize];
317
    theFont=[RTextView font];
318
    theFont=[[NSFontManager sharedFontManager] convertFont:theFont toSize:[fontSizeStepper intValue]];
319
    [RTextView setFont:theFont];
898 urbaneks 320
	 */
321
	[RTextView setFont: textFont];
568 iacus 322
	{
323
		NSMutableDictionary *md = [[RTextView typingAttributes] mutableCopy];
815 urbaneks 324
		[md setObject: [consoleColors objectAtIndex:iInputColor] forKey: @"NSColor"];
568 iacus 325
		[RTextView setTypingAttributes:[NSDictionary dictionaryWithDictionary:md]];
326
		[md release];
327
	}
590 urbaneks 328
	[RTextView setContinuousSpellCheckingEnabled:NO]; // force 'no spell checker'
1488 urbaneks 329
	[[RTextView textStorage] setDelegate:self];
899 urbaneks 330
 
783 urbaneks 331
	//	[RTextView changeColor: inputColor];
568 iacus 332
	[RTextView display];
333
	[self setupToolbar];
815 urbaneks 334
 
568 iacus 335
	[ RConsoleWindow setDocumentEdited:YES];
336
 
337
    //NSLog(@"RController: awake: done");
1328 urbaneks 338
 
339
	SLog(@" - setup timer");
568 iacus 340
	WDirtimer = [NSTimer scheduledTimerWithTimeInterval:0.5
575 urbaneks 341
												 target:self
342
											   selector:@selector(showWorkingDir:)
343
											   userInfo:0
344
												repeats:YES];
568 iacus 345
 
346
	hist=[[History alloc] init];
783 urbaneks 347
 
348
 
568 iacus 349
    BOOL WantThread = YES;
350
 
1328 urbaneks 351
	SLog(@" - setup stdout/err grabber");
568 iacus 352
    if(WantThread){ // re-route the stdout to our own file descriptor and use ConnectionCache on it
353
        int pfd[2];
354
        pipe(pfd);
355
        dup2(pfd[1], STDOUT_FILENO);
356
        close(pfd[1]);
357
 
358
        stdoutFD=pfd[0];
783 urbaneks 359
 
568 iacus 360
        pipe(pfd);
361
#ifndef PLAIN_STDERR
362
        dup2(pfd[1], STDERR_FILENO);
363
        close(pfd[1]);
364
#endif
365
 
366
        stderrFD=pfd[0];
783 urbaneks 367
 
568 iacus 368
		[self addConnectionLog];
369
    }
783 urbaneks 370
 
1328 urbaneks 371
	SLog(@" - set cwd and load history");
568 iacus 372
	[historyView setDoubleAction: @selector(historyDoubleClick:)];
783 urbaneks 373
 
568 iacus 374
	currentSize = [[RTextView textContainer] containerSize].width;
375
	//currentFontSize = [[RTextView font] pointSize];
376
	currentConsoleWidth = -1;
1191 urbaneks 377
	[[NSFileManager defaultManager] changeCurrentDirectoryPath: [[Preferences stringForKey:initialWorkingDirectoryKey withDefault:@"~"] stringByExpandingTildeInPath]];
1136 urbaneks 378
	if ([Preferences flagForKey:importOnStartupKey withDefault:YES]) {
1191 urbaneks 379
		[self doLoadHistory:nil];
1136 urbaneks 380
	}
1328 urbaneks 381
	SLog(@" - awake is done");
568 iacus 382
}
383
 
384
-(void) applicationDidFinishLaunching: (NSNotification *)aNotification
385
{
1328 urbaneks 386
	SLog(@"RController.applicationDidFinishLaunching");
387
	SLog(@" - initalizing R");
568 iacus 388
	if (![[REngine mainEngine] activate]) {
1045 urbaneks 389
		NSRunAlertPanel(NLS(@"Cannot start R"),[NSString stringWithFormat:NLS(@"Unable to start R: %@"), [[REngine mainEngine] lastError]],NLS(@"OK"),nil,nil);
568 iacus 390
		exit(-1);
391
	}
783 urbaneks 392
 
1328 urbaneks 393
	SLog(@" - set R options");
1146 urbaneks 394
	// force html-help, because that's the only format we can handle ATM
395
	[[REngine mainEngine] executeString: @"options(htmlhelp=TRUE)"];
1396 urbaneks 396
 
397
	SLog(@" - set default CRAN mirror");
398
	{
399
		NSString *url = [Preferences stringForKey:defaultCRANmirrorURLKey withDefault:@""];
400
		if (![url isEqualToString:@""])
401
			[[REngine mainEngine] executeString:[NSString stringWithFormat:@"try({ r <- getOption('repos'); r['CRAN']<-gsub('/$', '', \"%@\"); options(repos = r) },silent=TRUE)", url]];
402
	}
403
 
1328 urbaneks 404
	SLog(@" - clean up and flush console");
568 iacus 405
	[self setOptionWidth:YES];
406
	[RTextView setEditable:YES];
575 urbaneks 407
	[self flushROutput];
568 iacus 408
 
1328 urbaneks 409
	SLog(@" - setup notification and timers");
568 iacus 410
	[[NSNotificationCenter defaultCenter] 
411
		addObserver:self
412
		   selector:@selector(RConsoleDidResize:)
413
			   name:NSWindowDidResizeNotification
414
			 object: RConsoleWindow];
415
 
575 urbaneks 416
	timer = [NSTimer scheduledTimerWithTimeInterval:0.05
417
											 target:self
418
										   selector:@selector(otherEventLoops:)
419
										   userInfo:0
420
											repeats:YES];
421
	Flushtimer = [NSTimer scheduledTimerWithTimeInterval:0.5
422
												  target:self
423
												selector:@selector(flushTimerHook:)
424
												userInfo:0
425
												 repeats:YES];
783 urbaneks 426
 
899 urbaneks 427
	// once we're ready with the doc transition, the following will actually fire up the cconsole window
428
	//[[NSDocumentController sharedDocumentController] openUntitledDocumentOfType:@"Rcommand" display:YES];
429
 
1328 urbaneks 430
	SLog(@" - show main window");
899 urbaneks 431
	[RConsoleWindow makeKeyAndOrderFront:self];
904 urbaneks 432
	//[[REngine mainEngine] runDelayedREPL]; // start delayed REPL
899 urbaneks 433
	//CGPostKeyboardEvent(27, 53, 1); // post <ESC> to cause SIGINT and thus the actual start of REPL
434
 
1328 urbaneks 435
	SLog(@" - setup REPL trigger timer");
568 iacus 436
	if (!RLtimer)
437
		RLtimer = [NSTimer scheduledTimerWithTimeInterval:0.001
438
												   target:self
871 urbaneks 439
												 selector:@selector(kickstart:)
568 iacus 440
												 userInfo:0
1191 urbaneks 441
												  repeats:NO];
442
	appLaunched = YES;
1488 urbaneks 443
 
1328 urbaneks 444
	SLog(@" - done, ready to go");
568 iacus 445
}
446
 
1257 urbaneks 447
- (BOOL)appLaunched {
448
	return appLaunched;
449
}
450
 
871 urbaneks 451
- (void) kickstart:(id) sender {
904 urbaneks 452
	//kill(getpid(),SIGINT);
1488 urbaneks 453
	[self setStatusLineText:@""];
904 urbaneks 454
	[[REngine mainEngine] runREPL];
871 urbaneks 455
}
456
 
568 iacus 457
-(void) addConnectionLog
458
{
459
	NSPort *port1;
460
	NSPort *port2;
461
	NSArray *portArray;
462
	NSConnection *connectionToTransferServer;
463
 
464
	port1 = [NSPort port];
465
	port2 = [NSPort port];
466
	connectionToTransferServer = [[NSConnection alloc] initWithReceivePort:port1 sendPort:port2];
467
	[connectionToTransferServer setRootObject:self];
468
 
469
	portArray = [NSArray arrayWithObjects:port2, port1, nil];
470
	[NSThread detachNewThreadSelector:@selector(readThread:)
471
							 toTarget:self
472
						   withObject:portArray];
473
}
474
 
475
- (void) readThread: (NSArray *)portArray
476
{
477
    NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];
478
    NSConnection *connectionToController;
479
	RController *rc;
480
	unsigned int bufSize=2048;
871 urbaneks 481
    char *buf=(char*) malloc(bufSize+16);
587 urbaneks 482
    int n=0, pib=0, flushMark=bufSize-(bufSize>>2);
568 iacus 483
	int bufFD=0;
484
    fd_set readfds;
485
	struct timeval timv;
783 urbaneks 486
 
568 iacus 487
	timv.tv_sec=0; timv.tv_usec=300000; /* timeout */
783 urbaneks 488
 
568 iacus 489
	connectionToController = [NSConnection connectionWithReceivePort:[portArray objectAtIndex:0]
490
															sendPort:[portArray objectAtIndex:1]];
783 urbaneks 491
 
568 iacus 492
	rc = ((RController *)[connectionToController rootProxy]);
493
 
494
    fcntl(stdoutFD, F_SETFL, O_NONBLOCK);
495
    fcntl(stderrFD, F_SETFL, O_NONBLOCK);
496
    while (1) {
497
		int selr=0, maxfd=stdoutFD;
1328 urbaneks 498
		int validRootFD=-1;
568 iacus 499
        FD_ZERO(&readfds);
500
        FD_SET(stdoutFD,&readfds);
501
        FD_SET(stderrFD,&readfds); if(stderrFD>maxfd) maxfd=stderrFD;
1328 urbaneks 502
		if (rootFD!=-1) {
503
			validRootFD = rootFD; // we copy it as to not run into threading problems when it is changed while we're in the middle of processing
504
			FD_SET(rootFD,&readfds);
505
			if(rootFD>maxfd) maxfd=rootFD;
506
		}
568 iacus 507
        selr=select(maxfd+1, &readfds, 0, 0, &timv);
508
        if (FD_ISSET(stdoutFD, &readfds)) {
509
			if (bufFD!=0 && pib>0) {
871 urbaneks 510
				@try{
511
					[rc writeLogsWithBytes:buf length:pib type:bufFD];
512
				} @catch(NSException *ex) {
513
				}
568 iacus 514
				pib=0;
515
			}
516
			bufFD=0;
517
            while (pib<bufSize && (n=read(stdoutFD,buf+pib,bufSize-pib))>0)
518
				pib+=n;
519
			if (pib>flushMark) { // if we reach the flush mark, dump it
871 urbaneks 520
				@try{
568 iacus 521
					[rc writeLogsWithBytes:buf length:pib type:bufFD];
871 urbaneks 522
				} @catch(NSException *ex) {
523
				}
568 iacus 524
				pib=0;
525
            }
526
        } 
527
		if (FD_ISSET(stderrFD, &readfds)) {
528
			if (bufFD!=1 && pib>0) {
529
				[rc writeLogsWithBytes:buf length:pib type:bufFD];
530
				pib=0;
531
			}
532
			bufFD=1;
533
			while (pib<bufSize && (n=read(stderrFD,buf+pib,bufSize-pib))>0)
534
				pib+=n;
535
			if (pib>flushMark) { // if we reach the flush mark, dump it
871 urbaneks 536
				@try{
568 iacus 537
					[rc writeLogsWithBytes:buf length:pib type:bufFD];
871 urbaneks 538
				} @catch(NSException *ex) {
539
				}
568 iacus 540
				pib=0;
541
			}
542
		}
1328 urbaneks 543
		if (validRootFD!=-1 && FD_ISSET(validRootFD, &readfds)) {
568 iacus 544
			if (bufFD!=2 && pib>0) {
871 urbaneks 545
				@try{
546
					[rc writeLogsWithBytes:buf length:pib type:bufFD];
547
				} @catch(NSException *ex) {
548
				}
568 iacus 549
				pib=0;
550
			}
551
			bufFD=2;
1328 urbaneks 552
			while (pib<bufSize && (n=read(validRootFD,buf+pib,bufSize-pib))>0)
568 iacus 553
				pib+=n;
554
			if (n==0 || pib>flushMark) { // if we reach the flush mark, dump it
871 urbaneks 555
				@try{
568 iacus 556
					[rc writeLogsWithBytes:buf length:pib type:bufFD];
871 urbaneks 557
				} @catch(NSException *ex) {
558
				}
568 iacus 559
				pib=0;
560
			}
1328 urbaneks 561
			if (n==0) rootFD=-1; // we indicate EOF on the rootFD by setting it to -1
568 iacus 562
		}
563
		if ((forceStdFlush || selr==0) && pib>0) { // dump also if we got a timeout
871 urbaneks 564
			@try{
568 iacus 565
				[rc writeLogsWithBytes:buf length:pib type:bufFD];
871 urbaneks 566
			} @catch(NSException *ex) {
567
			}
568 iacus 568
			pib=0;
569
		}
570
    }
571
    free(buf);
572
 
573
    [pool release];
574
}
575
 
576
- (void) flushStdConsole
577
{
578
	fflush(stderr);
579
	fflush(stdout);
575 urbaneks 580
	forceStdFlush=YES;
568 iacus 581
}
582
 
583
- (void) addChildProcess: (pid_t) pid
584
{
585
	childPID=pid;
586
	if (pid>0 && toolbarStopItem) [toolbarStopItem setEnabled:YES];
587
}
588
 
589
- (void) rmChildProcess: (pid_t) pid
590
{
591
	childPID=0;
592
	if (!busyRFlag && toolbarStopItem) [toolbarStopItem setEnabled:NO];
575 urbaneks 593
	[self flushStdConsole];
568 iacus 594
}
595
 
596
// When you have the toolbar in text-only mode, this action is called
597
// by the toolbar item's menu to make the font bigger.  We just change the stepper control and
598
// call our -changeFontSize: action.
599
-(IBAction) fontSizeBigger:(id)sender
600
{
601
	if ([RConsoleWindow isKeyWindow]) {
602
		[fontSizeStepper setIntValue:[fontSizeStepper intValue]+1];
603
		[self changeFontSize:NULL];
604
	} else
605
		[[NSFontManager sharedFontManager] modifyFont:sender];
606
}
607
 
608
// When you have the toolbar in text-only mode, this action is called
609
// by the toolbar item's menu to make the font smaller.  We just change the stepper control and
610
// call our -changeFontSize: action.
611
-(IBAction) fontSizeSmaller:(id)sender
612
{
613
	if ([RConsoleWindow isKeyWindow]) {
614
		[fontSizeStepper setIntValue:[fontSizeStepper intValue]-1];
615
		[self changeFontSize:NULL];
616
	} else
617
		[[NSFontManager sharedFontManager] modifyFont:sender];
783 urbaneks 618
 
568 iacus 619
}
620
 
621
// This action is called to change the font size.  It's called by the NSPopUpButton in the toolbar item's 
622
// custom view, and also by the above routines called from the toolbar item's menu (in text-only mode).
623
-(IBAction) changeFontSize:(id)sender
624
{
625
    NSFont *theFont;
626
 
627
    [fontSizeField takeIntValueFrom:fontSizeStepper];
628
    theFont=[RTextView font];
629
    theFont=[[NSFontManager sharedFontManager] convertFont:theFont toSize:[fontSizeStepper intValue]];
630
    [RTextView setFont:theFont];
631
	[self setOptionWidth:NO];
632
	[[NSUserDefaults standardUserDefaults] setFloat: [fontSizeStepper intValue] forKey:FontSizeKey];
633
}
634
 
635
 
636
extern BOOL isTimeToFinish;
637
 
638
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app {
639
	if (![self windowShouldClose:self])
640
		return NSTerminateCancel;
641
 
642
	if(timer){
643
		[timer invalidate];
644
		timer = nil;
645
	}
783 urbaneks 646
 
568 iacus 647
	if(RLtimer){
648
		[RLtimer invalidate];
649
		RLtimer = nil;
650
	}
783 urbaneks 651
 
568 iacus 652
	if(Flushtimer){
653
		[Flushtimer invalidate];
654
		Flushtimer = nil;
655
	}
656
 
657
	if(WDirtimer){
658
		[WDirtimer invalidate];
659
		WDirtimer = nil;
660
	}
783 urbaneks 661
 
568 iacus 662
	return NSTerminateNow;
663
}
664
 
665
 
666
- (void) dealloc {
667
	[[NSNotificationCenter defaultCenter] removeObserver:self];
815 urbaneks 668
	[[Preferences sharedPreferences] removeDependent:self];
669
	[defaultConsoleColors release];
670
	[consoleColors release];
671
	[consoleColorsKeys release];
568 iacus 672
	[super dealloc];
673
}
674
 
575 urbaneks 675
- (void) flushTimerHook: (NSTimer*) source
676
{
677
	[self flushROutput];
678
}
679
 
680
- (void) flushROutput {
568 iacus 681
	if (writeBuffer!=writeBufferPos) {
815 urbaneks 682
		[self writeConsoleDirectly:[NSString stringWithUTF8String:writeBuffer] withColor:[consoleColors objectAtIndex:iOutputColor]];
568 iacus 683
		writeBufferPos=writeBuffer;
684
	}
575 urbaneks 685
}
686
 
687
- (void) handleFlushConsole {
688
	[self flushROutput];
568 iacus 689
	[self flushStdConsole];
690
}
691
 
692
/* this writes R output to the Console window, but indirectly by using a buffer */
693
- (void) handleWriteConsole: (NSString*) txt {
694
	const char *s = [txt UTF8String];
695
	int sl = strlen(s);
696
	int fits = writeBufferLen-(writeBufferPos-writeBuffer)-1;
697
 
698
	// let's flush the buffer if the new string is large and it would, but the buffer should be occupied
699
	if (fits<sl && fits>writeBufferHighWaterMark) {
575 urbaneks 700
		// for efficiency we're not using handleFlushConsole, because that would trigger stdxx flush, too
815 urbaneks 701
		[self writeConsoleDirectly:[NSString stringWithUTF8String:writeBuffer] withColor:[consoleColors objectAtIndex:iOutputColor]];
568 iacus 702
		writeBufferPos=writeBuffer;
703
		fits = writeBufferLen-1;
704
	}
705
 
706
	while (fits<sl) {	// ok, we're in a situation where we must split the string
707
		memcpy(writeBufferPos, s, fits);
708
		writeBufferPos[writeBufferLen-1]=0;
815 urbaneks 709
		[self writeConsoleDirectly:[NSString stringWithUTF8String:writeBuffer] withColor:[consoleColors objectAtIndex:iOutputColor]];
568 iacus 710
		sl-=fits; s+=fits;
711
		writeBufferPos=writeBuffer;
712
		fits=writeBufferLen-1;
713
	}
783 urbaneks 714
 
568 iacus 715
	strcpy(writeBufferPos, s);
716
	writeBufferPos+=sl;
717
 
718
	// flush the buffer if the low watermark is reached
719
	if (fits-sl<writeBufferLowWaterMark) {
815 urbaneks 720
		[self writeConsoleDirectly:[NSString stringWithUTF8String:writeBuffer] withColor:[consoleColors objectAtIndex:iOutputColor]];
568 iacus 721
		writeBufferPos=writeBuffer;
722
	}
723
}
724
 
725
/* this writes R output to the Console window directly, i.e. without using a buffer. Use handleWriteConsole: for the regular way. */
575 urbaneks 726
- (void) writeConsoleDirectly: (NSString*) txt withColor: (NSColor*) color{
871 urbaneks 727
	@synchronized(textViewSync) {
898 urbaneks 728
		RConsoleTextStorage *textStorage = (RConsoleTextStorage*) [RTextView textStorage];
871 urbaneks 729
		NSRange origSel = [RTextView selectedRange];
730
		unsigned tl = [txt length];
731
		if (tl>0) {
732
			unsigned oldCL=committedLength;
733
			/* NSLog(@"original: %d:%d, insertion: %d, length: %d, prompt: %d, commit: %d", origSel.location,
734
			origSel.length, outputPosition, tl, promptPosition, committedLength); */
898 urbaneks 735
			[textStorage beginEditing];
871 urbaneks 736
			committedLength=0;
898 urbaneks 737
			[textStorage insertText:txt atIndex:outputPosition withColor:color];
871 urbaneks 738
			if (outputPosition<=promptPosition) promptPosition+=tl;
739
			committedLength=oldCL;
740
			if (outputPosition<=committedLength) committedLength+=tl;
741
			if (outputPosition<=origSel.location) origSel.location+=tl;
742
			outputPosition+=tl;
898 urbaneks 743
			[textStorage endEditing];
871 urbaneks 744
			[RTextView setSelectedRange:origSel];
898 urbaneks 745
			[RTextView scrollRangeToVisible:origSel];
871 urbaneks 746
		}
575 urbaneks 747
	}
568 iacus 748
}
749
 
750
/* Just writes the prompt in a different color */
751
- (void)handleWritePrompt: (NSString*) prompt {
752
    [self handleFlushConsole];
871 urbaneks 753
	@synchronized(textViewSync) {
898 urbaneks 754
		RConsoleTextStorage *textStorage = (RConsoleTextStorage*) [RTextView textStorage];
755
		unsigned textLength = [textStorage length];
871 urbaneks 756
		int promptLength=[prompt length];
1257 urbaneks 757
//		NSLog(@"Prompt: %@", prompt);
898 urbaneks 758
		NSRange lr = [[textStorage string] lineRangeForRange:NSMakeRange(textLength,0)];
759
		[textStorage beginEditing];
871 urbaneks 760
		promptPosition=textLength;
761
		if (lr.location!=textLength) { // the prompt must be on the beginning of the line
898 urbaneks 762
			[textStorage insertText: @"\n" atIndex: textLength withColor:[consoleColors objectAtIndex:iPromptColor]];
763
			textLength = [textStorage length];
764
			promptLength++;
871 urbaneks 765
		}
766
 
767
		if (promptLength>0) {
898 urbaneks 768
			[textStorage insertText:prompt atIndex: textLength withColor:[consoleColors objectAtIndex:iPromptColor]];
871 urbaneks 769
			if (promptLength>1) // this is a trick to make sure that the insertion color doesn't change at the prompt
898 urbaneks 770
				[textStorage addAttribute:@"NSColor" value:[consoleColors objectAtIndex:iInputColor] range:NSMakeRange(promptPosition+promptLength-1, 1)];
771
			committedLength=promptPosition+promptLength;
871 urbaneks 772
		}
773
		committedLength=promptPosition+promptLength;
898 urbaneks 774
		[textStorage endEditing];
775
		{
776
			NSRange targetRange = NSMakeRange(committedLength,0);
777
			[RTextView setSelectedRange:targetRange];
778
			[RTextView scrollRangeToVisible:targetRange];
779
		}
568 iacus 780
	}
781
}
782
 
783
- (void)  handleProcessingInput: (char*) cmd
784
{
788 urbaneks 785
	NSString *s = [[NSString alloc] initWithUTF8String:cmd];
568 iacus 786
 
871 urbaneks 787
	@synchronized(textViewSync) {
788
		unsigned textLength = [[RTextView textStorage] length];
789
 
790
		[RTextView setSelectedRange:NSMakeRange(committedLength, textLength-committedLength)];
791
		[RTextView insertText:s];
792
		textLength = [[RTextView textStorage] length];
793
		[RTextView setTextColor:[consoleColors objectAtIndex:iInputColor] range:NSMakeRange(committedLength, textLength-committedLength)];
794
		outputPosition=committedLength=textLength;
901 urbaneks 795
 
796
		// remove undo actions to prevent undo across prompts
797
		[[RTextView undoManager] removeAllActions];
871 urbaneks 798
	}
799
 
788 urbaneks 800
	[s release];
568 iacus 801
 
1146 urbaneks 802
// the ?/help hack is no longer needed in 2.1
803
#if (R_VERSION < R_Version(2,1,0))
568 iacus 804
	if((*cmd == '?') || (!strncmp("help(",cmd,5))){ 
805
		[self openHelpFor: cmd];
806
		cmd[0] = '\n'; cmd[1] = 0;
807
	}
1146 urbaneks 808
#endif
568 iacus 809
}
810
 
811
- (char*) handleReadConsole: (int) addtohist
812
{
813
	if (currentConsoleInput) {
814
		[currentConsoleInput release];
815
		currentConsoleInput=nil;
816
	}
817
 
818
	while ([consoleInputQueue count]==0)
819
		[self doProcessEvents: YES];
820
 
821
	currentConsoleInput = [consoleInputQueue objectAtIndex:0];
822
	[consoleInputQueue removeObjectAtIndex:0];
783 urbaneks 823
 
568 iacus 824
	if (addtohist) {
1257 urbaneks 825
//		Figure out how to get hold of ParseStatus here!
826
		[hist commit:currentConsoleInput];
568 iacus 827
		[historyView reloadData];
828
	}
829
 
755 urbaneks 830
	{
831
		const char *c = [currentConsoleInput UTF8String];
832
		if (!c) return 0;
833
		if (strlen(c)>readConsTransBufferSize-1) { // grow as necessary
834
			free(readConsTransBuffer);
835
			readConsTransBufferSize = (strlen(c)+2048)&0xfffffc00;
836
			readConsTransBuffer = (char*) malloc(readConsTransBufferSize);
837
		} // we don't shrink the buffer if gets too large - we may want to think about that ...
838
 
839
		strcpy(readConsTransBuffer, c);
840
	}
841
	return readConsTransBuffer;
568 iacus 842
}
843
 
688 urbaneks 844
- (int) handleEdit: (char*) file
845
{
824 urbaneks 846
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
847
	NSString *fn = [[NSString stringWithUTF8String:file] stringByExpandingTildeInPath];
1420 urbaneks 848
	SLog(@"RController.handleEdit: %s", file);
849
 
824 urbaneks 850
	if (![[NSFileManager defaultManager] isReadableFileAtPath:fn]) {
851
		[pool release];
852
		return 0;
853
	}
854
	RDocument *document = [[RDocumentController sharedDocumentController] openRDocumentWithContentsOfFile: fn display:YES];
688 urbaneks 855
	[document setREditFlag: YES];
856
 
857
	NSEnumerator *e = [[document windowControllers] objectEnumerator];
858
	NSWindowController *wc = nil;
859
	while (wc = [e nextObject]) {
860
		NSWindow *window = [wc window];
861
		NSModalSession session = [NSApp beginModalSessionForWindow:window];
1136 urbaneks 862
		while([document hasREditFlag]) {
688 urbaneks 863
			[NSApp runModalSession:session];
1136 urbaneks 864
		}
688 urbaneks 865
		[NSApp endModalSession:session];
866
	}
867
 
824 urbaneks 868
	[pool release];
688 urbaneks 869
	return(0);
870
}
871
 
872
/* FIXME: the filename is not set for newvly created files */
873
- (int) handleEditFiles: (int) nfile withNames: (char**) file titles: (char**) wtitle pager: (char*) pager
874
{
875
	int    	i;
876
 
1420 urbaneks 877
	SLog(@"RController.handleEditFiles (%d of them, pager %s)", nfile, pager);
688 urbaneks 878
    if (nfile <=0) return 1;
879
 
929 urbaneks 880
    for (i = 0; i < nfile; i++) {
881
		NSString *fn = [[NSString stringWithUTF8String:file[i]] stringByExpandingTildeInPath];
882
		if([[NSFileManager defaultManager] fileExistsAtPath:fn])
883
			[[RDocumentController sharedDocumentController] openRDocumentWithContentsOfFile:fn display:true];
688 urbaneks 884
		else
1490 urbaneks 885
			[[NSDocumentController sharedDocumentController] newDocument: [RController sharedController]];
688 urbaneks 886
 
887
		NSDocument *document = [[NSDocumentController sharedDocumentController] currentDocument];
888
		if(wtitle[i]!=nil)
929 urbaneks 889
			[RDocument changeDocumentTitle: document Title: [NSString stringWithUTF8String:wtitle[i]]];
688 urbaneks 890
    }
891
	return 1;
892
}
893
 
894
- (int) handleShowFiles: (int) nfile withNames: (char**) file headers: (char**) headers windowTitle: (char*) wtitle pager: (char*) pages andDelete: (BOOL) del
895
{
896
	int    	i;
897
 
898
    if (nfile <=0) return 1;
1420 urbaneks 899
	SLog(@"RController.handleShowFiles (%d of them, title %s, pager %s)", nfile, wtitle, pages);
688 urbaneks 900
 
901
    for (i = 0; i < nfile; i++){
929 urbaneks 902
		NSString *fn = [[NSString stringWithUTF8String:file[i]] stringByExpandingTildeInPath];
1432 urbaneks 903
		RDocument *document = [[RDocumentController sharedDocumentController] openRDocumentWithContentsOfFile:fn display:NO];
904
		// don't dsiplay - we need to prevent the window controller from using highlighting
1420 urbaneks 905
		if (document) {
1432 urbaneks 906
			NSArray *wcs = [document windowControllers];
907
			if (wcs && [wcs count]>0) {
908
				SLog(@" - Disabling syntax highlighting for this document");
909
				[(RDocumentWinCtrl*) [wcs objectAtIndex:0] setPlain:YES];
910
			}
1420 urbaneks 911
			if(wtitle[i]!=nil)
912
				[RDocument changeDocumentTitle: document Title: [NSString stringWithUTF8String:wtitle]];
913
			[document setEditable: NO];
1432 urbaneks 914
			SLog(@" - finally show the document window");
915
			[document showWindows];
1420 urbaneks 916
		}
688 urbaneks 917
    }
918
	return 1;
919
}
920
 
921
//======== Cocoa Handler ======
922
 
923
- (int) handlePackages: (int) count withNames: (char**) name descriptions: (char**) desc URLs: (char**) url status: (BOOL*) stat
924
{
925
	[[PackageManager sharedController] updatePackages:count withNames:name descriptions:desc URLs:url status:stat];
926
	return 0;
927
}
928
 
1252 urbaneks 929
- (int) handleListItems: (int) count withNames: (char**) name status: (BOOL*) stat multiple: (BOOL) multiple title: (NSString*) title;
1212 iacus 930
{
1255 urbaneks 931
	return [[SelectList sharedController] selectList:count withNames:name status:stat multiple: multiple title:title];
1212 iacus 932
}
933
 
796 urbaneks 934
- (int) handleHelpSearch: (int) count withTopics: (char**) topics packages: (char**) pkgs descriptions: (char**) descs urls: (char**) urls title: (char*) title
935
{
936
	[[SearchTable sharedController] updateHelpSearch:count withTopics:topics packages:pkgs descriptions:descs urls:urls title:title];
937
	return 0;
938
}
939
 
688 urbaneks 940
- (BOOL*) handleDatasets: (int) count withNames: (char**) name descriptions: (char**) desc packages: (char**) pkg URLs: (char**) url
941
{
942
	[[DataManager sharedController] updateDatasets:count withNames:name descriptions:desc packages:pkg URLs:url];
943
	return 0; // we don't load the DS this way, we use REngine instead
944
}
945
 
946
- (int) handleInstalledPackages: (int) count withNames: (char**) name installedVersions: (char**) iver repositoryVersions: (char**) rver update: (BOOL*) stat label: (char*) label
947
{
948
	[[PackageInstaller sharedController] updateInstalledPackages:count withNames:name installedVersions:iver repositoryVersions:rver update:stat label:label];
949
	return 0;
950
}
951
 
952
- (int) handleSystemCommand: (char*) cmd
953
{	
954
	int cstat=-1;
955
	pid_t pid;
956
 
957
	if ([self getRootFlag]) {
958
		FILE *f;
959
		char *argv[3] = { "-c", cmd, 0 };
1328 urbaneks 960
		int res;
688 urbaneks 961
 		NSBundle *b = [NSBundle mainBundle];
962
		char *sushPath=0;
963
		if (b) {
964
			NSString *sush=[[b resourcePath] stringByAppendingString:@"/sush"];
965
			sushPath = (char*) malloc([sush cStringLength]+1);
966
			[sush getCString:sushPath maxLength:[sush cStringLength]];
967
		}
968
 
1328 urbaneks 969
		res = runRootScript(sushPath?sushPath:"/bin/sh",argv,&f,1);
970
		if (!res && f) {		
971
			int fd = fileno(f);
972
			if (fd != -1) {
973
				struct timespec peSleep = { 0, 50000000 }; // 50ms sleep
974
				[self setRootFD:fileno(f)];
975
 
976
				while (rootFD!=-1) { // readThread will reset rootFD to -1 when reaching EOF
977
					nanosleep(&peSleep, 0); // sleep at least 50ms between PE calls (they're expensive)
978
					Re_ProcessEvents();
979
				}
980
			}
981
		}
688 urbaneks 982
		if (sushPath) free(sushPath);
1328 urbaneks 983
		return res;
688 urbaneks 984
	}
985
 
986
	pid=fork();
987
	if (pid==0) {
988
		// int sr;
989
		// reset signal handlers
990
		signal(SIGINT, SIG_DFL);
991
		signal(SIGTERM, SIG_DFL);
992
		signal(SIGQUIT, SIG_DFL);
993
		signal(SIGALRM, SIG_DFL);
994
		signal(SIGCHLD, SIG_DFL);
1252 urbaneks 995
		execl("/bin/sh", "/bin/sh", "-c", cmd, NULL);
688 urbaneks 996
		exit(-1);
997
		//sr=system(cmd);
998
		//exit(WEXITSTATUS(sr));
999
	}
1000
	if (pid==-1) return -1;
1472 urbaneks 1001
 
1002
	{
1477 urbaneks 1003
		struct timespec peSleep = { 0, 100000000 }; // 100ms sleep
1472 urbaneks 1004
		while (1) {
1005
			pid_t w = waitpid(pid, &cstat, WNOHANG);
1006
			if (w!=0) break;
1007
			nanosleep(&peSleep, 0); // sleep at least 50ms between PE calls (they're expensive)
1008
			Re_ProcessEvents();
1009
		}
688 urbaneks 1010
	}
1490 urbaneks 1011
	[[RController sharedController] rmChildProcess: pid];
688 urbaneks 1012
	return cstat;
1013
}	
1014
 
1146 urbaneks 1015
- (int) handleCustomPrint: (char*) type withObject: (RSEXP*) obj
1016
{
1155 urbaneks 1017
	if (!obj) return -2;
1146 urbaneks 1018
	if (!strcmp(type, "help-files")) {
1155 urbaneks 1019
		RSEXP *x = [obj attr:@"topic"];
1020
		NSString *topic = @"<unknown>";
1021
		if (x && [x string]) topic = [x string];
1022
		[x release];
1023
		if ([obj type]==STRSXP && [obj length]>0) {
1024
			[[HelpManager sharedController] showHelpUsingFile: [obj string] topic: topic];
1490 urbaneks 1025
			[[self window] makeKeyWindow];
1026
			[[[HelpManager sharedController] window] makeKeyAndOrderFront:self];
1155 urbaneks 1027
		} else
1028
			NSBeginAlertSheet(NLS(@"Help topic not found"),NLS(@"OK"),nil,nil,[RTextView window],self,nil,NULL,NULL,[NSString stringWithFormat: NLS(@"Help for the topic \"%@\" was not found."), topic]);
1146 urbaneks 1029
	}
1030
 
1031
	return 0;
1032
}
1033
 
688 urbaneks 1034
//==========
1035
 
568 iacus 1036
- (BOOL)windowShouldClose:(id)sender
1037
{
1038 urbaneks 1038
	[[RDocumentController sharedDocumentController] closeAllDocumentsWithDelegate:self didCloseAllSelector:@selector(didCloseAll:) contextInfo:nil];	
1039
	return NO;
1040
}	
568 iacus 1041
 
1038 urbaneks 1042
- (void)didCloseAll:(id)sender {
1438 urbaneks 1043
	[Preferences commit];
1191 urbaneks 1044
	[self doSaveHistory:nil];
917 urbaneks 1045
	NSBeginAlertSheet(NLS(@"Closing R session"),NLS(@"Save"),NLS(@"Don't Save"),NLS(@"Cancel"),[RTextView window],self,@selector(shouldCloseDidEnd:returnCode:contextInfo:),NULL,NULL,NLS(@"Save workspace image?"));
568 iacus 1046
}
1047
 
1048
/* this gets called by the "wanna save?" sheet on window close */
1049
- (void) shouldCloseDidEnd:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo {
1438 urbaneks 1050
	[Preferences commit];
568 iacus 1051
    if (returnCode==NSAlertDefaultReturn)
1052
		[[REngine mainEngine] executeString:@"quit(\"yes\")"];
1053
    if (returnCode==NSAlertAlternateReturn)
1054
		[[REngine mainEngine] executeString:@"quit(\"no\")"];
1055
}
1056
 
1057
/*  This is used to send commands through the GUI, i.e. from menus 
783 urbaneks 1058
The input replaces what the user is currently typing.
568 iacus 1059
*/
1060
- (void) sendInput: (NSString*) text {
1061
	[self consoleInput:text interactive:YES];
1062
	/*
783 urbaneks 1063
	 unsigned textLength = [[RTextView textStorage] length];
1064
	 [RTextView setSelectedRange:NSMakeRange(textLength, 0)];
1065
	 NSEvent* event = [NSEvent keyEventWithType:NSKeyDown
1066
									   location:NSMakePoint(0,0)
1067
								  modifierFlags:0
1068
									  timestamp:0
1069
								   windowNumber:[RConsoleWindow windowNumber]
1070
										context:nil
1071
									 characters:@"\n"
1072
					charactersIgnoringModifiers:nil
1073
									  isARepeat:NO
1074
										keyCode:nil
1075
		 ];
1076
	 [NSApp postEvent:event atStart:YES];
568 iacus 1077
	 */
1078
}
1079
 
1080
/* These two routines are needed to update the History TableView */
1081
- (int)numberOfRowsInTableView: (NSTableView *)tableView
1082
{
1083
	return [[hist entries] count];
1084
}
1085
 
1086
- (id)tableView: (NSTableView *)tableView
1087
		objectValueForTableColumn: (NSTableColumn *)tableColumn
783 urbaneks 1088
			row: (int)row
568 iacus 1089
{
783 urbaneks 1090
	return (NSString*) [[hist entries]  objectAtIndex: row];
568 iacus 1091
}
1092
 
1093
/*  Clears the history  and updates the TableView */
1094
 
1095
- (IBAction)doClearHistory:(id)sender
1096
{
1457 urbaneks 1097
	SLog(@"RController.doClearHistory");
568 iacus 1098
	[hist resetAll];
1099
	[historyView reloadData];
1100
}
1101
 
1102
- (IBAction)doLoadHistory:(id)sender
1103
{
1252 urbaneks 1104
	NSString *fname=nil;
1457 urbaneks 1105
	SLog(@"RController.doLoadHistory");
1191 urbaneks 1106
	if (sender) {
1107
		NSOpenPanel *op = [NSOpenPanel openPanel];
1108
		[op setDirectory:[[[NSFileManager defaultManager] currentDirectoryPath] stringByExpandingTildeInPath]];
1109
		[op setTitle:NLS(@"Choose history File")];
1110
		if([op runModalForTypes: [NSArray arrayWithObject:@"history"]] == NSOKButton)
1111
			fname = [op filename];
1112
	} else
1113
		fname = [[Preferences stringForKey:historyFileNamePathKey
1114
							   withDefault: @".Rhistory"] stringByExpandingTildeInPath];
1457 urbaneks 1115
	SLog(@" - history file to load: %@", fname);
1191 urbaneks 1116
	if(fname != nil){
1457 urbaneks 1117
		fname = [[[[NSFileManager defaultManager] currentDirectoryPath] stringByExpandingTildeInPath] stringByAppendingString:[NSString stringWithFormat:@"/%@", fname]];
1257 urbaneks 1118
		[self doClearHistory:nil];
1457 urbaneks 1119
 
1120
		SLog(@" - cleared history, reload with: %@", fname);
1191 urbaneks 1121
		if ([[NSFileManager defaultManager] fileExistsAtPath: fname]) {
1457 urbaneks 1122
			FILE *rhist = fopen([fname UTF8String], "r");
1123
			if (!rhist) {
1191 urbaneks 1124
				NSLog(NLS(@"Can't open history file %2"), fname);
1125
			} else {
1457 urbaneks 1126
				NSString *entry = nil;
1127
				char c[1024];
1128
 
1129
				c[1023]=0;
1130
				while(fgets(c, 1023, rhist) && *c) {
1131
					int i = strlen(c);
1132
					BOOL multiline = NO;
1133
 
1134
					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
1135
					if (!*c) continue; // skip blank lines (is that intended? what about "foo#\n\nbla\n"?)
1136
					if (multiline=(c[i-1]=='#')) c[i-1]='\n';
1137
					if (entry)
1138
						entry = [entry stringByAppendingString:[NSString stringWithUTF8String:c]];
1139
					else
1140
						entry = [NSString stringWithUTF8String:c];
1141
					if (!multiline) {
1142
						[hist commit:entry];
1143
						entry=nil;
1191 urbaneks 1144
					}
1145
				}
1457 urbaneks 1146
				if (entry) [hist commit:entry]; // just being paranoid if someone edited the file manually
1147
				fclose(rhist);
1191 urbaneks 1148
			}
1149
		}		
1150
		[historyView scrollRowToVisible:[historyView numberOfRows]];
1151
		[historyView reloadData];
568 iacus 1152
	}
1153
}
1154
 
1136 urbaneks 1155
- (void)doSaveHistory:(id)sender {
1252 urbaneks 1156
	NSString *fname = nil;
1457 urbaneks 1157
	FILE *rhist;
1158
 
1191 urbaneks 1159
	if (sender) {
1160
		NSSavePanel *sp = [NSSavePanel savePanel];
1161
		[sp setDirectory:[[[NSFileManager defaultManager] currentDirectoryPath] stringByExpandingTildeInPath]];
1162
		[sp setRequiredFileType:@"history"];
1163
		[sp setTitle:NLS(@"Save history File")];
1164
		if([sp runModal] == NSOKButton) fname = [sp filename];		
1165
	} else 
1166
		fname = [[Preferences stringForKey:historyFileNamePathKey
1167
							   withDefault: @".Rhistory"] stringByExpandingTildeInPath];
1457 urbaneks 1168
 
1169
	SLog(@"RController.doSaveHistory (file %@)", fname);
1170
	rhist = fopen([fname UTF8String], "w");
1171
	if (!rhist) {
1191 urbaneks 1172
		NSLog(NLS(@"Can't open history file %2"), fname);
1173
	} else {
1457 urbaneks 1174
		NSEnumerator *enumerator = [[hist entries] objectEnumerator];
1175
		NSString *entry;
1176
 
1177
		while (entry = [enumerator nextObject]) {
1178
			if ([entry rangeOfString:@"\n" options:NSLiteralSearch].location!=NSNotFound) { // add # before \n for multi-line strings
1179
				entry = [entry mutableCopy];
1180
				[(NSMutableString*)entry replaceOccurrencesOfString:@"\n" withString:@"#\n" options:NSLiteralSearch range:NSMakeRange(0,[entry length])];
1191 urbaneks 1181
			}
1457 urbaneks 1182
			fputs([entry UTF8String], rhist); // not 100% safe
1183
			fputc('\n', rhist); // trailing \n
1191 urbaneks 1184
		}
1457 urbaneks 1185
 
1191 urbaneks 1186
		fclose(rhist);	
568 iacus 1187
	}
1188
}
1189
 
1190
/*  On double-click on items of the History TableView, the item is pasted into the console
783 urbaneks 1191
at current cursor position
568 iacus 1192
*/
1193
- (IBAction)historyDoubleClick:(id)sender {
1194
	NSString *cmd;
1257 urbaneks 1195
	int index = [historyView selectedRow];
568 iacus 1196
	if(index == -1) return;
1197
 
1198
	cmd = [[hist entries] objectAtIndex:index];
1199
	[self consoleInput:cmd interactive:NO];
1200
	[RConsoleWindow makeFirstResponder:RTextView];
1201
}
1202
 
1257 urbaneks 1203
- (IBAction)historyDeleteEntry:(id)sender {
1204
	int index = [historyView selectedRow];
1205
	if((index >= 0) && (index < [[hist entries] count]))
1206
		[hist deleteEntry:index];
1207
	[historyView reloadData];
1208
}
1209
 
568 iacus 1210
/*  This routine is intended to "cat" some text to the R Console without
783 urbaneks 1211
issuing the newline.
568 iacus 1212
- (void) consolePaste: (NSString*) text {
1213
	unsigned textLength = [[RTextView textStorage] length];
1214
	[RTextView setSelectedRange:NSMakeRange(textLength, 0)];
1215
	[RTextView insertText:text];
1216
}
1217
*/
1218
/* This function is used by two threads to write  stderr and/or stdout to the console
783 urbaneks 1219
outputType: 0 = stdout, 1 = stderr, 2 = stdout/err as root
568 iacus 1220
*/
1221
- (void) writeLogsWithBytes: (char*) buf length: (int) len type: (int) outputType
1222
{
815 urbaneks 1223
	NSColor *color=(outputType==0)?[consoleColors objectAtIndex:iStdoutColor]:((outputType==1)?[consoleColors objectAtIndex:iStderrColor]:[consoleColors objectAtIndex:iRootColor]);
788 urbaneks 1224
	buf[len]=0; /* this MAY be dangerous ... */
1225
	NSString *s = [[NSString alloc] initWithUTF8String:buf];
575 urbaneks 1226
	[self flushROutput];
788 urbaneks 1227
	[self writeConsoleDirectly:s withColor:color];
1228
	[s release];
1229
}
568 iacus 1230
 
1490 urbaneks 1231
+ (RController*) sharedController{
568 iacus 1232
	return sharedRController;
1233
}
1234
 
1235
/* console input - the string passed here is handled as if it was typed on the console */
1236
- (void) consoleInput: (NSString*) cmd interactive: (BOOL) inter
1237
{
871 urbaneks 1238
	@synchronized(textViewSync) {
1239
		if (!inter) {
1240
			int textLength = [[RTextView textStorage] length];
1241
			if (textLength>committedLength)
1242
				[RTextView replaceCharactersInRange:NSMakeRange(committedLength,textLength-committedLength) withString:@""];
1243
			[RTextView setSelectedRange:NSMakeRange(committedLength,0)];
1244
			[RTextView insertText: cmd];
1245
			textLength = [[RTextView textStorage] length];
1246
			[RTextView setTextColor:[consoleColors objectAtIndex:iInputColor] range:NSMakeRange(committedLength,textLength-committedLength)];
1247
		}
1248
 
1249
		if (inter) {
1250
			if ([cmd characterAtIndex:[cmd length]-1]!='\n') cmd=[cmd stringByAppendingString: @"\n"];
1251
			[consoleInputQueue addObject:[[NSString alloc] initWithString:cmd]];
1488 urbaneks 1252
			[self setStatusLineText:@""];
871 urbaneks 1253
		}
568 iacus 1254
	}
1255
}
1256
 
1257
- (BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector {
1258
    BOOL retval = NO;
783 urbaneks 1259
 
568 iacus 1260
	//NSLog(@"RTextView commandSelector: %@\n", NSStringFromSelector(commandSelector));
1261
 
1262
    if (@selector(insertNewline:) == commandSelector) {
1263
        unsigned textLength = [[textView textStorage] length];
1264
		[textView setSelectedRange:NSMakeRange(textLength,0)];
1265
        if (textLength >= committedLength) {
1266
			[textView insertText:@"\n"];
1267
			textLength = [[textView textStorage] length];
1268
			[self consoleInput: [[textView attributedSubstringFromRange:NSMakeRange(committedLength, textLength - committedLength)] string] interactive: YES];
1269
			return(YES);
1270
        }
1271
        retval = YES;
1272
    }
1257 urbaneks 1273
 
568 iacus 1274
	// ---- history browsing ----
1275
	if (@selector(moveUp:) == commandSelector) {
1276
        unsigned textLength = [[textView textStorage] length];        
1277
        NSRange sr=[textView selectedRange];
1278
        if (sr.location==committedLength || sr.location==textLength) {
1279
            NSRange rr=NSMakeRange(committedLength, textLength-committedLength);
1280
            NSString *text = [[textView attributedSubstringFromRange:rr] string];
1281
            if ([hist isDirty]) {
1282
                [hist updateDirty: text];
1283
            }
1284
            NSString *news = [hist prev];
1285
            if (news!=nil) {
1286
                [news retain];
1287
                sr.length=0; sr.location=committedLength;
1288
                [textView setSelectedRange:sr];
1289
                [textView replaceCharactersInRange:rr withString:news];
1290
                [textView insertText:@""];
1291
                [news release];
1292
            }
1293
            retval = YES;
1294
        }
1295
    }
1296
    if (@selector(moveDown:) == commandSelector) {
1297
        unsigned textLength = [[textView textStorage] length];        
1298
        NSRange sr=[textView selectedRange];
1299
        if ((sr.location==committedLength || sr.location==textLength) && ![hist isDirty]) {
1300
            NSRange rr=NSMakeRange(committedLength, textLength-committedLength);
1301
            NSString *news = [hist next];
1302
            if (news==nil) news=@""; else [news retain];
1303
            sr.length=0; sr.location=committedLength;
1304
            [textView setSelectedRange:sr];
1305
            [textView replaceCharactersInRange:rr withString:news];
1306
            [textView insertText:@""];
1307
            [news release];
1308
            retval = YES;
1309
        }
1310
    }
1311
 
1312
	// ---- make sure the user won't accidentally get out of the input line ----
1313
 
1314
	if (@selector(moveToBeginningOfParagraph:) == commandSelector || @selector(moveToBeginningOfLine:) == commandSelector) {
1315
        [textView setSelectedRange: NSMakeRange(committedLength,0)];
1316
        retval = YES;
1317
    }
783 urbaneks 1318
 
568 iacus 1319
	if (@selector(moveToBeginningOfParagraphAndModifySelection:) == commandSelector || @selector(moveToBeginningOfLineAndModifySelection:) == commandSelector) {
1320
		// FIXME: this kills the selection - we should retain it ...
1321
        [textView setSelectedRange: NSMakeRange(committedLength,0)];
1322
        retval = YES;
1323
    }
1324
 
1325
	if (@selector(moveWordLeft:) == commandSelector || @selector(moveLeft:) == commandSelector ||
1326
		@selector(moveWordLeftAndModifySelection:) == commandSelector || @selector(moveLeftAndModifySelection:) == commandSelector) {
1327
        NSRange sr=[textView selectedRange];
1328
		if (sr.location==committedLength) return YES;
1329
	}
1330
 
1331
	// ---- code/file completion ----
1332
 
1333
	if (@selector(insertTab:) == commandSelector) {
974 urbaneks 1334
		[textView complete:self];
1335
		retval = YES;
568 iacus 1336
	}
1337
 
1338
	// ---- cancel ---
1339
 
1340
	if (@selector(cancel:) == commandSelector) {
1341
		[self breakR:self];
1342
		retval = YES;
1343
	}
1344
 
1345
	return retval;
1346
}
783 urbaneks 1347
 
1488 urbaneks 1348
- (void)textStorageDidProcessEditing:(NSNotification *)aNotification {
1349
	NSTextStorage *ts = [aNotification object];
1350
	NSString *s = [ts string];
1351
	NSRange sr = [RTextView selectedRange];
1352
 
1353
	// check for a typed (
1603 urbaneks 1354
	if (argsHints && sr.location>committedLength && sr.length==0 && sr.location>0 && sr.location<[s length] && [s characterAtIndex:sr.location]=='(') {
1488 urbaneks 1355
		int i = sr.location-1;
1356
		unichar c = [s characterAtIndex:i];
1357
		BOOL hasLit = NO;
1358
		while ((c>='0' && c<='9') || (c>='a' && c<='z') || (c>='A' && c<='Z') || c=='.' || c=='_') {
1359
			if (!hasLit && ((c>='a' && c<='z') || (c>='A' && c<='Z'))) hasLit=YES;
1360
			i--;
1361
			if (i<0) break;
1362
			c = [s characterAtIndex:i];
1363
		}
1364
		i++;
1365
		if (hasLit && sr.location>i)
1366
			[self hintForFunction: [s substringWithRange:NSMakeRange(i,sr.location-i)]];
1367
	}
1368
}
1369
 
1370
- (BOOL) hintForFunction: (NSString*) fn
1371
{
1372
	BOOL success = NO;
1499 urbaneks 1373
	if (insideR>0) {
1374
		[self setStatusLineText:NLS(@"(arguments lookup is disabled while R is busy)")];
1375
		return NO;
1376
	}
1562 urbaneks 1377
	if (![[REngine mainEngine] beginProtected]) {
1378
		[self setStatusLineText:NLS(@"(arguments lookup is disabled while R is busy)")];
1379
		return NO;		
1380
	}
1488 urbaneks 1381
	RSEXP *x = [[REngine mainEngine] evaluateString:[NSString stringWithFormat:@"try(gsub('\\\\s+',' ',paste(capture.output(print(args(%@))),collapse='')),silent=TRUE)", fn]];
1382
	if (x) {
1383
		NSString *res = [x string];
1384
		if (res && [res length]>10 && [res hasPrefix:@"function"]) {
1385
			if ([res hasSuffix:@" NULL"]) res=[res substringToIndex:[res length]-5];
1386
			res = [fn stringByAppendingString:[res substringFromIndex:9]];
1387
			success = YES;
1388
			[self setStatusLineText:res];
1389
		}
1390
		[x release];
1391
	}
1562 urbaneks 1392
	[[REngine mainEngine] endProtected];
1488 urbaneks 1393
	return success;
1394
}
1395
 
568 iacus 1396
/* Allow changes only for uncommitted text */
1397
- (BOOL)textView:(NSTextView *)textView shouldChangeTextInRange:(NSRange)affectedCharRange replacementString:(NSString *)replacementString {
1436 urbaneks 1398
	if (replacementString && /* on font change we get nil replacementString which is ok to pass through */
1399
		affectedCharRange.location < committedLength) { /* if the insertion is outside editable scope, append at the end */
623 urbaneks 1400
		[textView setSelectedRange:NSMakeRange([[textView textStorage] length],0)];
1401
		[textView insertText:replacementString];
1402
		return NO;
1403
	}
1404
	return YES;
568 iacus 1405
}
783 urbaneks 1406
 
974 urbaneks 1407
- (NSArray *)textView:(NSTextView *)textView completions:(NSArray *)words forPartialWordRange:(NSRange)charRange indexOfSelectedItem:(int *)index 
1408
{
1409
	NSRange sr=[textView selectedRange];
1499 urbaneks 1410
	SLog(@"completion attempt; cursor at %d, complRange: %d-%d, commit: %d", sr.location, charRange.location, charRange.location+charRange.length, committedLength);
974 urbaneks 1411
	//sr=charRange;
1412
	int bow=sr.location;
1413
	if (bow>committedLength) {
1414
		while (bow>committedLength) bow--;
1415
		{
1416
			NSString *rep=nil;
1417
			NSRange er = NSMakeRange(bow,sr.location-bow);
1418
			NSString *text = [[textView attributedSubstringFromRange:er] string];
1419
 
1420
			// first we need to find out whether we're in a text part or code part
1421
			unichar c;
1422
			int tl = [text length], tp=0, quotes=0, dquotes=0, lastQuote=-1;
1423
			while (tp<tl) {
1424
				c=[text characterAtIndex:tp];
1425
				if (c=='\\') tp++; // skip the next char after a backslash (we don't have to worry about \023 and friends)
1426
				else {
1427
					if (dquotes==0 && c=='\'') {
1428
						quotes^=1;
1429
						if (quotes) lastQuote=tp;
1430
					}
1431
					if (quotes==0 && c=='"') {
1432
						dquotes^=1;
1433
						if (dquotes) lastQuote=tp;
1434
					}
1435
				}
1436
				tp++;
1437
			}
1438
 
1439
			if (quotes+dquotes>0) { // if we're inside any quotes, use file completion
1440
				//rep=[FileCompletion complete:[text substringFromIndex:lastQuote+1]];
1441
				er.location+=lastQuote+1;
1442
				er.length-=lastQuote+1;
1443
				return [FileCompletion completeAll:[text substringFromIndex:lastQuote+1] cutPrefix:0];
1444
			} else { // otherwise use code completion
1445
				int s = [text length]-1;
1446
				c = [text characterAtIndex:s];
1447
				while (((c>='a')&&(c<='z'))||((c>='A')&&(c<='Z'))||((c>='0')&&(c<='9'))||c=='.') {
1448
					s--;
1449
					if (s==-1) break;
1450
					c = [text characterAtIndex:s];
1451
				}
1452
				s++;
1453
				er.location+=s; er.length-=s;
1454
				//rep=[CodeCompletion complete:[text substringFromIndex:s]];
1455
				*index=0;
1488 urbaneks 1456
				{
1457
					NSArray *ca = [CodeCompletion completeAll:[text substringFromIndex:s] cutPrefix:charRange.location-er.location];
1458
					if (ca && [ca count]==1) [self hintForFunction:[[ca objectAtIndex:0] substringToIndex:[[ca objectAtIndex:0] length]-1]];
1459
					return ca;
1460
				}
974 urbaneks 1461
			}
1462
 
1463
			// ok, by now we should get "rep" if completion is possible and "er" modified to match the proper part
1464
			if (rep!=nil) {
1465
				*index=0;
1466
				return [NSArray arrayWithObjects: rep, @"dummy", nil];
1467
				//[textView replaceCharactersInRange:er withString:rep];
1468
			}
1469
		}
1470
	}
1471
	return nil;
1472
}
1473
 
568 iacus 1474
- (void) handleBusy: (BOOL) isBusy {
1475
    if (isBusy)
1476
        [progressWheel startAnimation:self];
1477
    else
1478
        [progressWheel stopAnimation:self];
783 urbaneks 1479
 
568 iacus 1480
	busyRFlag = isBusy;
1481
	if (toolbarStopItem) {
1482
		if (isBusy || childPID>0)
1483
			[toolbarStopItem setEnabled:YES];
1484
		else
1485
			[toolbarStopItem setEnabled:NO];
1486
	}
1487
}
1488
 
1489
- (void)  handleShowMessage: (char*) msg
1490
{
1457 urbaneks 1491
	NSRunAlertPanel(NLS(@"R Message"),[NSString stringWithUTF8String:msg],NLS(@"OK"),nil,nil);
568 iacus 1492
}
1493
 
1494
- (IBAction)flushconsole:(id)sender {
1495
	[self handleFlushConsole];
1496
}
1497
 
1498
- (IBAction)otherEventLoops:(id)sender {
1499
	R_runHandlers(R_InputHandlers, R_checkActivity(0, 1));
1500
}
1501
 
1502
 
1503
- (IBAction)newQuartzDevice:(id)sender {
849 urbaneks 1504
	NSString *width = [Preferences stringForKey:quartzPrefPaneWidthKey withDefault: @"4.5"];
1505
	NSString *height = [Preferences stringForKey:quartzPrefPaneHeightKey withDefault: @"4.5"];
1506
	NSString *cmd = [[[[@"quartz(display=\"\",width=" stringByAppendingString:width]
1507
		stringByAppendingString:@",height="] stringByAppendingString:height] stringByAppendingString:@")"];
1508
	[[REngine mainEngine] executeString:cmd];
568 iacus 1509
}
1510
 
1511
- (IBAction)breakR:(id)sender{
1512
	if (childPID)
1513
		kill(childPID, SIGINT);
1514
	else
1515
		onintr();
1516
}
1517
 
1518
- (IBAction)quitR:(id)sender{
1519
	[self windowShouldClose:RConsoleWindow];
1520
}
1521
 
577 urbaneks 1522
- (IBAction)makeConsoleKey:(id)sender
1523
{
1524
	[RConsoleWindow makeKeyAndOrderFront:sender];
1525
}
1526
 
568 iacus 1527
- (IBAction)toggleHistory:(id)sender{
1136 urbaneks 1528
	NSDrawerState state = [HistoryDrawer state];
1529
	if (NSDrawerOpeningState == state || NSDrawerOpenState == state) {
1530
		[HistoryDrawer close];
1531
	} else {
1532
		[HistoryDrawer open];
1533
	}
568 iacus 1534
}
1535
 
1536
- (IBAction)toggleAuthentication:(id)sender{
1537
	BOOL isOn = [self getRootFlag];
1538
 
1539
	if (isOn) {
1540
		removeRootAuthorization();
1541
		[self setRootFlag:NO];
1542
	} else {
1543
		if (requestRootAuthorization(1)) return;
1544
		[self setRootFlag:YES];
1545
	}
1546
}
1547
 
577 urbaneks 1548
- (IBAction)newDocument:(id)sender{
568 iacus 1549
	[[NSDocumentController sharedDocumentController] newDocument: sender];
1550
}
1551
 
905 urbaneks 1552
- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename {
1457 urbaneks 1553
	SLog(@" - application:openFile:%@ called", (NSString *)filename);
1554
	NSString *dirname = @"";
1191 urbaneks 1555
	BOOL isDir;
1556
	BOOL flag = [Preferences flagForKey:enforceInitialWorkingDirectoryKey withDefault:NO];
1557
	NSFileManager *manager = [NSFileManager defaultManager];
1558
	if ([manager fileExistsAtPath:filename isDirectory:&isDir] && isDir){
1559
		if (!flag && !appLaunched) {
1560
			[manager changeCurrentDirectoryPath:[filename stringByExpandingTildeInPath]];
1561
			[self showWorkingDir:nil];				
1457 urbaneks 1562
			[self doClearHistory:nil];
1563
			[self doLoadHistory:nil];
1191 urbaneks 1564
		}
1565
	} else {
1566
		if (!flag && !appLaunched) {
1252 urbaneks 1567
			int i, j=-1;
1191 urbaneks 1568
			for (i=[filename length]-1 ; i>=0 ; i--) {
1569
				if ([filename characterAtIndex:i] == '/') {
1570
					j = i; i = -1;
1571
				};
1572
			}
1457 urbaneks 1573
			dirname = [filename substringWithRange: NSMakeRange(0, j+1)];
1191 urbaneks 1574
			[manager changeCurrentDirectoryPath:[dirname stringByExpandingTildeInPath]];
1575
			[self showWorkingDir:nil];					
1576
		}
1457 urbaneks 1577
		[self doClearHistory:nil];
1578
		[self doLoadHistory:nil];
1191 urbaneks 1579
		BOOL openInEditor = [Preferences flagForKey:editOrSourceKey withDefault: YES];
1580
		if (openInEditor || appLaunched)
1581
			[[RDocumentController sharedDocumentController] openDocumentWithContentsOfFile: filename display:YES];
1582
		else
1583
			[self sendInput:[NSString stringWithFormat:@"source(\"%@\")",[filename stringByExpandingTildeInPath]]];
1584
	}
1457 urbaneks 1585
	SLog(@" - application:openFile:%@ with wd: <%@> done", (NSString *)filename, dirname);
905 urbaneks 1586
	return YES;
1587
}
1588
 
577 urbaneks 1589
- (IBAction)openDocument:(id)sender{
568 iacus 1590
	[[NSDocumentController sharedDocumentController] openDocument: sender];
1591
}
1592
 
577 urbaneks 1593
- (IBAction)saveDocumentAs:(id)sender{
1594
	NSDocument *cd = [[NSDocumentController sharedDocumentController] currentDocument];
1595
 
1596
	if (cd)
1597
		[cd saveDocumentAs:sender];
1598
	else {
1599
		int answer;
1600
		NSSavePanel *sp;
1601
		sp = [NSSavePanel savePanel];
1602
		[sp setRequiredFileType:@"txt"];
917 urbaneks 1603
		[sp setTitle:NLS(@"Save R Console To File")];
577 urbaneks 1604
		answer = [sp runModalForDirectory:nil file:@"R Console.txt"];
1605
 
1606
		if(answer == NSOKButton) {
1607
			[[RTextView string] writeToFile:[sp filename] atomically:YES];
1608
		}
1609
	}
568 iacus 1610
}
1611
 
1612
- (IBAction)saveDocument:(id)sender{
577 urbaneks 1613
	NSDocument *cd = [[NSDocumentController sharedDocumentController] currentDocument];
568 iacus 1614
 
577 urbaneks 1615
	if (cd)
1616
		[cd saveDocument:sender];
1617
	else // for the console this is the same as Save As ..
1618
		[self saveDocumentAs:sender];
568 iacus 1619
}
1620
 
1621
- (int) handleChooseFile:(char *)buf len:(int)len isNew:(int)isNew
1622
{
1623
	int answer;
1624
	NSSavePanel *sp;
1625
	NSOpenPanel *op;
783 urbaneks 1626
 
568 iacus 1627
	buf[0] = '\0';
1628
	if(isNew==1){
1629
		sp = [NSSavePanel savePanel];
917 urbaneks 1630
		[sp setTitle:NLS(@"Choose New File Name")];
568 iacus 1631
		answer = [sp runModalForDirectory:nil file:nil];
783 urbaneks 1632
 
568 iacus 1633
		if(answer == NSOKButton) {
1634
			if([sp filename] != nil){
1457 urbaneks 1635
				CFStringGetCString((CFStringRef)[sp filename], buf, len-1,  kCFStringEncodingUTF8); 
568 iacus 1636
				buf[len] = '\0';
1637
			}
1638
		}
1639
	} else {
1640
		op = [NSOpenPanel openPanel];
917 urbaneks 1641
		[op setTitle:NLS(@"Choose File")];
568 iacus 1642
		answer = [op runModalForDirectory:nil file:nil];
783 urbaneks 1643
 
568 iacus 1644
		if(answer == NSOKButton) {
1645
			if([op filename] != nil){
1457 urbaneks 1646
				CFStringGetCString((CFStringRef)[op filename], buf, len-1,  kCFStringEncodingUTF8 ); 
568 iacus 1647
				buf[len] = '\0';
1648
			}
1649
		}
1457 urbaneks 1650
	}
1651
	return strlen(buf); // is is used? it's potentially incorrect...
1652
}
568 iacus 1653
 
789 urbaneks 1654
- (void) loadFile:(NSString *)fname
568 iacus 1655
{
1490 urbaneks 1656
	int res = [[RController sharedController] isImageData:fname];
568 iacus 1657
 
1658
	switch(res){
1659
		case -1:
783 urbaneks 1660
			NSLog(@"cannot open file");
1661
			break;
1662
 
568 iacus 1663
		case 0:
789 urbaneks 1664
			[self sendInput: [NSString stringWithFormat:@"load(\"%@\")",fname]];
783 urbaneks 1665
			break;
1666
 
568 iacus 1667
		case 1:
789 urbaneks 1668
			[self sendInput: [NSString stringWithFormat:@"source(\"%@\")",fname]];
783 urbaneks 1669
			break;	
568 iacus 1670
		default:
783 urbaneks 1671
			break; 
568 iacus 1672
	}
1673
}
1674
 
1675
// FIXME: is this really sufficient? what about compressed files?
1676
/*  isImageData:	returns -1 on error, 0 if the file is RDX2 or RDX1, 
783 urbaneks 1677
1 otherwise.
568 iacus 1678
*/	
789 urbaneks 1679
- (int)isImageData:(NSString *)fname
568 iacus 1680
{
789 urbaneks 1681
	NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:fname];
1682
	NSData *header;
1683
	char buf[5];
1684
 
1685
	if (!fh)
1686
		return -1;
1687
 
1688
	header = [fh readDataOfLength:4];
1689
	[fh closeFile];
568 iacus 1690
 
789 urbaneks 1691
	if (!header || [header length]<4)
1692
		return -1;
1693
 
1694
	memcpy(buf, [header bytes], 4);
1695
 
1696
	buf[4]=0;
1697
	if( (strcmp(buf,"RDX2")==0) || ((strcmp(buf,"RDX1")==0)))
1698
		return(0);
1699
	return(1);
568 iacus 1700
}
1701
 
1702
- (void) doProcessEvents: (BOOL) blocking {
1703
	NSEvent *event;
783 urbaneks 1704
 
568 iacus 1705
	if (blocking){
1706
		event = [NSApp nextEventMatchingMask:NSAnyEventMask
1707
								   untilDate:[NSDate distantFuture]
1708
									  inMode:NSDefaultRunLoopMode
1709
									 dequeue:YES];
1710
		[NSApp sendEvent:event];	
1711
	} else {
1712
		while( (event = [NSApp nextEventMatchingMask:NSAnyEventMask
1713
										   untilDate:[NSDate dateWithTimeIntervalSinceNow:0.0001]
1714
											  inMode:NSDefaultRunLoopMode 
1715
											 dequeue:YES]))
1716
			[NSApp sendEvent:event];
1717
	}
1718
	return;
1719
}
1720
 
1721
- (void) handleProcessEvents{
1722
	[self doProcessEvents: NO];
1723
}
1724
 
783 urbaneks 1725
 
568 iacus 1726
/* 
1727
This method calls the showHelpFor method of the Help Manager which opens
783 urbaneks 1728
 the internal html browser/help system of R.app
1729
 This method is called from ReadConsole.
568 iacus 1730
 
783 urbaneks 1731
 The input C string 'topic' is parsed and the behaviour is the following:
1732
 
1733
 topic = ?something  => showHelpFor:@"something"
1734
 topic = help(something) => showHelpFor:@"something"
1735
 topic = help(something); print(anotherthing);   =>  showHelpFor:@"something"
1736
 
1737
 which means that all the rest of the input is discarded.
1738
 No error message or warning are raised.
1739
 */
568 iacus 1740
 
1741
- (void) openHelpFor: (char *) topic 
1742
{
1743
	char tmp[300];
1744
	int i;
783 urbaneks 1745
 
1136 urbaneks 1746
//	NSLog(@"openHelpFor: <%@>", [NSString stringWithCString:topic]);
1747
	if(topic[0] == '?' && (strlen(topic)>2))
1748
		[[HelpManager sharedController] showHelpFor: 
1749
			[NSString stringWithCString:topic+1 length:strlen(topic)-2]];
568 iacus 1750
	if(strncmp("help(",topic,5)==0){
1751
		for(i=5;i<strlen(topic); i++){
1752
			if(topic[i]==')')
1753
				break;
1754
			tmp[i-5] = topic[i];
1755
		}
1756
		tmp[i-5] = '\0';
796 urbaneks 1757
		[[HelpManager sharedController] showHelpFor: [NSString stringWithCString:tmp]];
568 iacus 1758
	}
1759
}
1760
 
1761
- (void) setupToolbar {
783 urbaneks 1762
 
568 iacus 1763
    // Create a new toolbar instance, and attach it to our document window 
783 urbaneks 1764
	toolbar = [[[NSToolbar alloc] initWithIdentifier: RToolbarIdentifier] autorelease];
568 iacus 1765
 
1766
    // Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults 
1767
    [toolbar setAllowsUserCustomization: YES];
1768
    [toolbar setAutosavesConfiguration: YES];
1769
    [toolbar setDisplayMode: NSToolbarDisplayModeIconOnly];
1770
 
1771
    // We are the delegate
1772
    [toolbar setDelegate: self];
1773
 
1774
    // Attach the toolbar to the document window 
1775
    [RConsoleWindow setToolbar: toolbar];
1776
}
1777
 
1778
- (NSToolbarItem *) toolbar: (NSToolbar *)toolbar itemForItemIdentifier: (NSString *) itemIdent willBeInsertedIntoToolbar:(BOOL) willBeInserted {
1779
    // Required delegate method:  Given an item identifier, this method returns an item 
1780
    // The toolbar will use this method to obtain toolbar items that can be displayed in the customization sheet, or in the toolbar itself 
1781
    NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier: itemIdent] autorelease];
1782
 
1783
    if ([itemIdent isEqual: SaveDocToolbarItemIdentifier]) {
577 urbaneks 1784
		// Set the text label to be displayed in the toolbar and customization palette 
1045 urbaneks 1785
		[toolbarItem setLabel: NLS(@"Save")];
1786
		[toolbarItem setPaletteLabel: NLS(@"Save Console Window")];
1787
		[toolbarItem setToolTip: NLS(@"Save R console window")];
577 urbaneks 1788
		[toolbarItem setImage: [NSImage imageNamed: @"SaveDocumentItemImage"]];
1789
		[toolbarItem setTarget: self];
1790
		[toolbarItem setAction: @selector(saveDocument:)];
1791
    } else if ([itemIdent isEqual: NewEditWinToolbarItemIdentifier]) {
1792
		// Set the text label to be displayed in the toolbar and customization palette 
1045 urbaneks 1793
		[toolbarItem setLabel: NLS(@"New Document")];
1794
		[toolbarItem setPaletteLabel: NLS(@"New Document")];
1795
		[toolbarItem setToolTip: NLS(@"Create a new, empty document in the editor")];
577 urbaneks 1796
		[toolbarItem setImage: [NSImage imageNamed: @"emptyDoc"]];
1797
		[toolbarItem setTarget: self];
1798
		[toolbarItem setAction: @selector(newDocument:)];
783 urbaneks 1799
 
568 iacus 1800
    } else  if ([itemIdent isEqual: X11ToolbarItemIdentifier]) {
1045 urbaneks 1801
		[toolbarItem setLabel: NLS(@"Start X11")];
1802
		[toolbarItem setPaletteLabel: NLS(@"Start X11 Server")];
1803
		[toolbarItem setToolTip: NLS(@"Start the X11 window server to allow R using X11 device and Tcl/Tk")];
577 urbaneks 1804
		[toolbarItem setImage: [NSImage imageNamed: @"X11"]];
1805
		[toolbarItem setTarget: self];
1806
		[toolbarItem setAction: @selector(runX11:)];
783 urbaneks 1807
 
568 iacus 1808
    }  else  if ([itemIdent isEqual: SetColorsToolbarItemIdentifier]) {
1045 urbaneks 1809
		[toolbarItem setLabel: NLS(@"Set Colors")];
1810
		[toolbarItem setPaletteLabel: NLS(@"Set R Colors")];
1811
		[toolbarItem setToolTip: NLS(@"Set R console colors")];
568 iacus 1812
		[toolbarItem setImage: [NSImage imageNamed: @"colors"]];
1813
		[toolbarItem setTarget: self];
1814
		[toolbarItem setAction: @selector(openColors:)];
783 urbaneks 1815
 
568 iacus 1816
    } else  if ([itemIdent isEqual: LoadFileInEditorToolbarItemIdentifier]) {
1045 urbaneks 1817
		[toolbarItem setLabel: NLS(@"Open In Editor")];
1818
		[toolbarItem setPaletteLabel: NLS(@"Open In Editor")];
1819
		[toolbarItem setToolTip: NLS(@"Open document in editor")];
577 urbaneks 1820
		[toolbarItem setImage: [NSImage imageNamed: @"RDoc"]];
1821
		[toolbarItem setTarget: self];
1822
		[toolbarItem setAction: @selector(openDocument:)];
783 urbaneks 1823
 
568 iacus 1824
    } else  if ([itemIdent isEqual: SourceRCodeToolbarIdentifier]) {
1045 urbaneks 1825
		[toolbarItem setLabel: NLS(@"Source/Load")];
1826
		[toolbarItem setPaletteLabel: NLS(@"Source or Load in R")];
1827
		[toolbarItem setToolTip: NLS(@"Source script or load data in R")];
577 urbaneks 1828
		[toolbarItem setImage: [NSImage imageNamed: @"sourceR"]];
1829
		[toolbarItem setTarget: self];
626 iacus 1830
		[toolbarItem setAction: @selector(sourceOrLoadFile:)];
783 urbaneks 1831
 
568 iacus 1832
    } else if([itemIdent isEqual: NewQuartzToolbarItemIdentifier]) {
1045 urbaneks 1833
		[toolbarItem setLabel: NLS(@"Quartz")];
1834
		[toolbarItem setPaletteLabel: NLS(@"Quartz")];
1835
		[toolbarItem setToolTip: NLS(@"Open a new Quartz device window")];
577 urbaneks 1836
		[toolbarItem setImage: [NSImage imageNamed: @"quartz"]];
1837
		[toolbarItem setTarget: self];
1838
		[toolbarItem setAction: @selector(newQuartzDevice:) ];
783 urbaneks 1839
 
568 iacus 1840
	} else if([itemIdent isEqual: InterruptToolbarItemIdentifier]) {
1045 urbaneks 1841
		[toolbarItem setLabel: NLS(@"Stop")];
1842
		[toolbarItem setPaletteLabel: NLS(@"Stop")];
577 urbaneks 1843
		toolbarStopItem = toolbarItem;
1045 urbaneks 1844
		[toolbarItem setToolTip: NLS(@"Interrupt current R computation")];
577 urbaneks 1845
		[toolbarItem setImage: [NSImage imageNamed: @"stop"]];
1846
		[toolbarItem setTarget: self];
1847
		[toolbarItem setAction: @selector(breakR:) ];
783 urbaneks 1848
 
568 iacus 1849
	}  else if([itemIdent isEqual: FontSizeToolbarItemIdentifier]) {
1045 urbaneks 1850
		[toolbarItem setLabel: NLS(@"Font Size")];
1851
		[toolbarItem setPaletteLabel: NLS(@"Font Size")];
1852
		[toolbarItem setToolTip: NLS(@"Change the size of R console font")];
577 urbaneks 1853
		[toolbarItem setTarget: self];
1854
		[toolbarItem performSelector:@selector(setView:) withObject:fontSizeView];
1855
		[toolbarItem setAction:NULL];
1856
		[toolbarItem setView:fontSizeView];
1857
		if ([toolbarItem view]!=NULL)
1858
		{
1859
			[toolbarItem setMinSize:[[toolbarItem view] bounds].size];
1860
			[toolbarItem setMaxSize:[[toolbarItem view] bounds].size];
1861
		}
783 urbaneks 1862
 
568 iacus 1863
	}  else if([itemIdent isEqual: NewQuartzToolbarItemIdentifier]) {
1045 urbaneks 1864
		[toolbarItem setLabel: NLS(@"Quartz")];
1865
		[toolbarItem setPaletteLabel: NLS(@"Quartz")];
1866
		[toolbarItem setToolTip: NLS(@"Open a new Quartz device window")];
577 urbaneks 1867
		[toolbarItem setImage: [NSImage imageNamed: @"quartz"]];
1868
		[toolbarItem setTarget: self];
1869
		[toolbarItem setAction: @selector(newQuartzDevice:) ];
783 urbaneks 1870
 
568 iacus 1871
	} else if([itemIdent isEqual: ShowHistoryToolbarItemIdentifier]) {
1045 urbaneks 1872
		[toolbarItem setLabel: NLS(@"History")];
1873
		[toolbarItem setPaletteLabel: NLS(@"History")];
1874
		[toolbarItem setToolTip: NLS(@"Show/Hide R command history")];
577 urbaneks 1875
		[toolbarItem setImage: [NSImage imageNamed: @"history"]];
1876
		[toolbarItem setTarget: self];
1136 urbaneks 1877
		[toolbarItem setAction: @selector(toggleHistory:) ];			
568 iacus 1878
	} else if([itemIdent isEqual: AuthenticationToolbarItemIdentifier]) {
1045 urbaneks 1879
		[toolbarItem setLabel: NLS(@"Authentication")];
1880
		[toolbarItem setPaletteLabel: NLS(@"Authentication")];
1881
		[toolbarItem setToolTip: NLS(@"Authorize R to run system commands as root")];
577 urbaneks 1882
		[toolbarItem setImage: [NSImage imageNamed: @"lock-locked"]];
1883
		[toolbarItem setTarget: self];
1884
		[toolbarItem setAction: @selector(toggleAuthentication:) ];
783 urbaneks 1885
 
568 iacus 1886
	} else if([itemIdent isEqual: QuitRToolbarItemIdentifier]) {
1045 urbaneks 1887
		[toolbarItem setLabel: NLS(@"Quit")];
1888
		[toolbarItem setPaletteLabel: NLS(@"Quit")];
1889
		[toolbarItem setToolTip: NLS(@"Quit R")];
577 urbaneks 1890
		[toolbarItem setImage: [NSImage imageNamed: @"quit"]];
1891
		[toolbarItem setTarget: self];
1892
		[toolbarItem setAction: @selector(quitR:) ];
783 urbaneks 1893
 
568 iacus 1894
	} else {
783 urbaneks 1895
		// itemIdent refered to a toolbar item that is not provide or supported by us or cocoa 
1896
		// Returning nil will inform the toolbar this kind of item is not supported 
1897
		toolbarItem = nil;
568 iacus 1898
    }
1899
    return toolbarItem;
1900
}
1901
 
1902
 
1903
- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) toolbar {
1904
    // Required delegate method:  Returns the ordered list of items to be shown in the toolbar by default    
1905
    // If during the toolbar's initialization, no overriding values are found in the user defaults, or if the
1906
    // user chooses to revert to the default items this set will be used 
1907
    return [NSArray arrayWithObjects:	InterruptToolbarItemIdentifier, SourceRCodeToolbarIdentifier,
577 urbaneks 1908
		NewQuartzToolbarItemIdentifier, X11ToolbarItemIdentifier,
1909
		NSToolbarSeparatorItemIdentifier,
1910
		AuthenticationToolbarItemIdentifier, ShowHistoryToolbarItemIdentifier,
1911
		SetColorsToolbarItemIdentifier,
1912
		NSToolbarSeparatorItemIdentifier, /* SaveDocToolbarItemIdentifier, */
1913
		LoadFileInEditorToolbarItemIdentifier,
1914
		NewEditWinToolbarItemIdentifier, NSToolbarPrintItemIdentifier, 
1915
		NSToolbarSeparatorItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier,
1916
		QuitRToolbarItemIdentifier, nil];
568 iacus 1917
}
1918
 
1919
- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) toolbar {
1920
    // Required delegate method:  Returns the list of all allowed items by identifier.  By default, the toolbar 
1921
    // does not assume any items are allowed, even the separator.  So, every allowed item must be explicitly listed   
1922
    // The set of allowed items is used to construct the customization palette 
1923
    return [NSArray arrayWithObjects: 	QuitRToolbarItemIdentifier, AuthenticationToolbarItemIdentifier, ShowHistoryToolbarItemIdentifier, 
577 urbaneks 1924
		InterruptToolbarItemIdentifier, NewQuartzToolbarItemIdentifier, /* SaveDocToolbarItemIdentifier, */
1925
		NewEditWinToolbarItemIdentifier, LoadFileInEditorToolbarItemIdentifier,
1926
		NSToolbarPrintItemIdentifier, NSToolbarCustomizeToolbarItemIdentifier,
1927
		NSToolbarFlexibleSpaceItemIdentifier, NSToolbarSpaceItemIdentifier, 
1928
		NSToolbarSeparatorItemIdentifier, X11ToolbarItemIdentifier,
1929
		SetColorsToolbarItemIdentifier,
1930
		FontSizeToolbarItemIdentifier, SourceRCodeToolbarIdentifier, nil];
568 iacus 1931
}
1932
 
1933
- (void) toolbarWillAddItem: (NSNotification *) notif {
1934
    // Optional delegate method:  Before an new item is added to the toolbar, this notification is posted.
1935
    // This is the best place to notice a new item is going into the toolbar.  For instance, if you need to 
1936
    // cache a reference to the toolbar item or need to set up some initial state, this is the best place 
1937
    // to do it.  The notification object is the toolbar to which the item is being added.  The item being 
1938
    // added is found by referencing the @"item" key in the userInfo 
1939
    NSToolbarItem *addedItem = [[notif userInfo] objectForKey: @"item"];
1940
    if ([[addedItem itemIdentifier] isEqual: NSToolbarPrintItemIdentifier]) {
1045 urbaneks 1941
		[addedItem setToolTip: NLS(@"Print this document")];
568 iacus 1942
		[addedItem setTarget: self];
1943
    }
1944
}  
1945
 
1946
- (void) toolbarDidRemoveItem: (NSNotification *) notif {
1947
    // Optional delegate method:  After an item is removed from a toolbar, this notification is sent.   This allows 
1948
    // the chance to tear down information related to the item that may have been cached.   The notification object
1949
    // is the toolbar from which the item is being removed.  The item being added is found by referencing the @"item"
1950
    // key in the userInfo 
783 urbaneks 1951
	// NSToolbarItem *removedItem = [[notif userInfo] objectForKey: @"item"];
568 iacus 1952
}
1953
 
1954
- (BOOL) validateToolbarItem: (NSToolbarItem *) toolbarItem {
1955
    // Optional method:  This message is sent to us since we are the target of some toolbar item actions 
1956
    // (for example:  of the save items action) 
1957
    BOOL enable = NO;
1958
    if ([[toolbarItem itemIdentifier] isEqual: SaveDocToolbarItemIdentifier]) {
1959
		enable = [RConsoleWindow isDocumentEdited];
1960
    } else if ([[toolbarItem itemIdentifier] isEqual: SourceRCodeToolbarIdentifier]) {
1961
		enable = YES;
1962
    } else if ([[toolbarItem itemIdentifier] isEqual: X11ToolbarItemIdentifier]) {
1963
		enable = YES;
1964
    } else if ([[toolbarItem itemIdentifier] isEqual: SetColorsToolbarItemIdentifier]) {
1965
		enable = YES;
1966
    } else if ([[toolbarItem itemIdentifier] isEqual: LoadFileInEditorToolbarItemIdentifier]) {
1967
		enable = YES;
1968
    } else if ([[toolbarItem itemIdentifier] isEqual: NewEditWinToolbarItemIdentifier]) {
1969
		enable = YES;
1970
    } else if ([[toolbarItem itemIdentifier] isEqual: NSToolbarPrintItemIdentifier]) {
1971
		enable = YES;
1972
    } else if ([[toolbarItem itemIdentifier] isEqual: NewQuartzToolbarItemIdentifier]) {
1973
		enable = YES;
1974
    } else if ([[toolbarItem itemIdentifier] isEqual: InterruptToolbarItemIdentifier]) {
1975
		enable = (busyRFlag || (childPID>0));
1976
	} else if ([[toolbarItem itemIdentifier] isEqual: ShowHistoryToolbarItemIdentifier]) {
1977
		enable = YES;
1978
	} else if ([[toolbarItem itemIdentifier] isEqual: AuthenticationToolbarItemIdentifier]) {
1979
		enable = YES;
1980
	} else if ([[toolbarItem itemIdentifier] isEqual: QuitRToolbarItemIdentifier]) {
1981
		enable = YES;
1982
    }		
1983
    return enable;
1984
}
1985
 
1986
 
1987
/* This is needed to force the NSDocument to know when edited windows are dirty */
1988
- (void) RConsoleDidResize: (NSNotification *)notification{
1989
	[self setOptionWidth:NO];
1990
}
1991
 
1992
- (void) setOptionWidth:(BOOL)force;
1993
{
1994
	float newSize = [[RTextView textContainer] containerSize].width;
1995
	float newFontSize = [[RTextView font] pointSize];
1996
	if((newSize != currentSize) | (newFontSize != currentFontSize) | force){
1997
		float newConsoleWidth = 1.5*newSize/newFontSize-1.0;
1998
		if((int)newConsoleWidth != (int)currentConsoleWidth){
1999
			R_SetOptionWidth(newConsoleWidth);
2000
			currentSize = newSize;	
2001
			currentFontSize = newFontSize;
2002
			currentConsoleWidth = newConsoleWidth;
2003
		}
2004
	}
2005
}
2006
 
2007
-(IBAction) checkForUpdates:(id)sender{
2008
	[[REngine mainEngine] executeString: @"Rapp.updates()"];
2009
}
2010
 
2011
-(IBAction) getWorkingDir:(id)sender
2012
{
2013
	[self sendInput:@"getwd()"];
783 urbaneks 2014
	//	[[REngine mainEngine] evaluateString: @"getwd()"];
2015
 
568 iacus 2016
}
2017
 
2018
-(IBAction) resetWorkingDir:(id)sender
2019
{
820 urbaneks 2020
	[[NSFileManager defaultManager] changeCurrentDirectoryPath: [[Preferences stringForKey:@"initialWorkingDirectoryKey" withDefault:@"~"] stringByExpandingTildeInPath]];
2021
	[self showWorkingDir:sender];
568 iacus 2022
}
2023
 
2024
-(IBAction) setWorkingDir:(id)sender
2025
{
2026
	NSOpenPanel *op;
2027
	int answer;
872 urbaneks 2028
 
568 iacus 2029
	op = [NSOpenPanel openPanel];
2030
	[op setCanChooseDirectories:YES];
2031
	[op setCanChooseFiles:NO];
1045 urbaneks 2032
	[op setTitle:NLS(@"Choose New Working Directory")];
783 urbaneks 2033
 
820 urbaneks 2034
	answer = [op runModalForDirectory:[[NSFileManager defaultManager] currentDirectoryPath] file:nil types:[NSArray arrayWithObject:@""]];
783 urbaneks 2035
 
820 urbaneks 2036
	if(answer == NSOKButton && [op directory] != nil)
2037
		[[NSFileManager defaultManager] changeCurrentDirectoryPath:[[op directory] stringByExpandingTildeInPath]];
2038
	[self showWorkingDir:sender];
568 iacus 2039
}
2040
 
2041
- (IBAction) showWorkingDir:(id)sender
2042
{
2043
	[WDirView setEditable:YES];
820 urbaneks 2044
	[WDirView setStringValue: [[[NSFileManager defaultManager] currentDirectoryPath] stringByAbbreviatingWithTildeInPath]];
568 iacus 2045
	[WDirView setEditable:NO];
2046
}
2047
 
2048
 
2049
 
2050
- (IBAction)installFromDir:(id)sender
2051
{
2052
	NSOpenPanel *op;
2053
	int answer;
2054
 
2055
	op = [NSOpenPanel openPanel];
2056
	[op setCanChooseDirectories:YES];
2057
	[op setCanChooseFiles:NO];
1045 urbaneks 2058
	[op setTitle:NLS(@"Select Package Directory")];
568 iacus 2059
 
2060
	answer = [op runModalForDirectory:nil file:nil types:[NSArray arrayWithObject:@""]];
2061
	[op setCanChooseDirectories:NO];
2062
	[op setCanChooseFiles:YES];		
783 urbaneks 2063
 
568 iacus 2064
	if(answer == NSOKButton) 
2065
		if([op directory] != nil)
2066
			[[REngine mainEngine] executeString: [NSString stringWithFormat:@"install.from.file(pkg=\"%@\")",[op directory]] ];
2067
}
2068
 
2069
- (IBAction)installFromBinary:(id)sender
2070
{
2071
	[[REngine mainEngine] executeString: @"install.from.file(binary=TRUE)" ];
2072
}
2073
 
2074
- (IBAction)installFromSource:(id)sender
2075
{
2076
	[[REngine mainEngine] executeString:@"install.from.file(binary=FALSE)" ];
2077
}
2078
 
2079
- (IBAction)togglePackageInstaller:(id)sender
2080
{
688 urbaneks 2081
	[[PackageInstaller sharedController] show];
568 iacus 2082
}
2083
 
2084
- (IBAction)toggleWSBrowser:(id)sender
2085
{
2086
	[WSBrowser toggleWorkspaceBrowser];
2087
	[[REngine mainEngine] executeString:@"browseEnv(html=F)"];
783 urbaneks 2088
 
568 iacus 2089
}
2090
 
2091
- (IBAction)loadWorkSpace:(id)sender
2092
{
2093
	[self sendInput:@"load(\".RData\")"];
783 urbaneks 2094
	//	[[REngine mainEngine] evaluateString:@"load(\".RData\")" ];
2095
 
568 iacus 2096
}
2097
 
2098
- (IBAction)saveWorkSpace:(id)sender
2099
{
2100
	[self sendInput:@"save.image()"];
783 urbaneks 2101
	//	[[REngine mainEngine] evaluateString:@"save.image()"];
568 iacus 2102
 
2103
}
2104
 
2105
- (IBAction)loadWorkSpaceFile:(id)sender
2106
{
2107
	[[REngine mainEngine] executeString:@"load(file.choose())"];
2108
}					
2109
 
2110
- (IBAction)saveWorkSpaceFile:(id)sender
2111
{
622 iacus 2112
	[[REngine mainEngine] executeString: @"save.image(file=file.choose(TRUE))"];
568 iacus 2113
}
2114
 
2115
- (IBAction)showWorkSpace:(id)sender{
2116
	[self sendInput:@"ls()"];
2117
}
2118
 
2119
- (IBAction)clearWorkSpace:(id)sender
2120
{
1045 urbaneks 2121
	NSBeginAlertSheet(NLS(@"Clear Workspace"), NLS(@"Yes"), NLS(@"No") , nil, RConsoleWindow, self, @selector(shouldClearWS:returnCode:contextInfo:), NULL, NULL,
2122
					  NLS(@"All objects in the workspace will be removed. Are you sure you want to proceed?"));
568 iacus 2123
}
2124
 
2125
/* this gets called by the "wanna save?" sheet on window close */
2126
- (void) shouldClearWS:(NSWindow *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo {
2127
    if (returnCode==NSAlertDefaultReturn)
2128
		[[REngine mainEngine] executeString: @"rm(list=ls())"];
2129
}
2130
 
2131
- (IBAction)togglePackageManager:(id)sender
2132
{
688 urbaneks 2133
	if ([[PackageManager sharedController] count]==0)
568 iacus 2134
		[[REngine mainEngine] executeString:@"package.manager()"];
688 urbaneks 2135
	else
2136
		[[PackageManager sharedController] show];
568 iacus 2137
}
2138
 
2139
- (IBAction)toggleDataManager:(id)sender
2140
{
688 urbaneks 2141
	if ([[DataManager sharedController] count]==0) {
2142
		[[DataManager sharedController] show];
568 iacus 2143
		[[REngine mainEngine] executeString: @"data.manager()"];
688 urbaneks 2144
	} else
2145
		[[DataManager sharedController] show];
568 iacus 2146
}
2147
 
2148
 
2149
-(IBAction) runX11:(id)sender{
577 urbaneks 2150
	system("open -a X11.app");
568 iacus 2151
}
783 urbaneks 2152
 
568 iacus 2153
-(IBAction) openColors:(id)sender{
815 urbaneks 2154
	[prefsCtrl selectPaneWithIdentifier:@"Colors"];
2155
	[prefsCtrl showWindow:self];
2156
	[[prefsCtrl window] makeKeyAndOrderFront:self];
568 iacus 2157
}
2158
 
2159
- (IBAction)performHelpSearch:(id)sender {
2160
    if ([[sender stringValue] length]>0) {
1136 urbaneks 2161
		[[HelpManager sharedController] showHelpFor:[sender stringValue]];
568 iacus 2162
        [helpSearch setStringValue:@""];
2163
    }
2164
}
2165
 
626 iacus 2166
- (IBAction)sourceOrLoadFile:(id)sender
2167
{
2168
	int answer;
2169
	NSOpenPanel *op;
2170
	op = [NSOpenPanel openPanel];
1045 urbaneks 2171
	[op setTitle:NLS(@"R File to Source/Load")];
626 iacus 2172
	answer = [op runModalForTypes:nil];
2173
 
2174
	if (answer==NSOKButton)
789 urbaneks 2175
		[self loadFile:[op filename]];
626 iacus 2176
}
2177
 
577 urbaneks 2178
- (IBAction)sourceFile:(id)sender
2179
{
2180
	int answer;
2181
	NSOpenPanel *op;
2182
	op = [NSOpenPanel openPanel];
1045 urbaneks 2183
	[op setTitle:NLS(@"R File to Source")];
577 urbaneks 2184
	answer = [op runModalForTypes:nil];
783 urbaneks 2185
 
582 urbaneks 2186
	if (answer==NSOKButton)
2187
		[self sendInput:[NSString stringWithFormat:@"source(\"%@\")",[op filename]]];
577 urbaneks 2188
}
568 iacus 2189
 
2190
- (IBAction)printDocument:(id)sender
2191
{
2192
	NSPrintInfo *printInfo;
2193
	NSPrintInfo *sharedInfo;
2194
	NSPrintOperation *printOp;
2195
	NSMutableDictionary *printInfoDict;
2196
	NSMutableDictionary *sharedDict;
2197
 
2198
	sharedInfo = [NSPrintInfo sharedPrintInfo];
2199
	sharedDict = [sharedInfo dictionary];
2200
	printInfoDict = [NSMutableDictionary dictionaryWithDictionary:
2201
		sharedDict];
2202
 
2203
	printInfo = [[NSPrintInfo alloc] initWithDictionary: printInfoDict];
2204
	[printInfo setHorizontalPagination: NSAutoPagination];
2205
	[printInfo setVerticalPagination: NSAutoPagination];
2206
	[printInfo setVerticallyCentered:NO];
2207
 
2208
	printOp = [NSPrintOperation printOperationWithView:RTextView 
2209
											 printInfo:printInfo];
2210
	[printOp setShowPanels:YES];
2211
	[printOp runOperation];
2212
}
2213
 
2214
- (IBAction) setDefaultColors:(id)sender {
815 urbaneks 2215
	int i = 0, ccs = [consoleColorsKeys count];
2216
	[[Preferences sharedPreferences] beginBatch];
2217
	while (i<ccs) {
2218
		[Preferences setKey:[consoleColorsKeys objectAtIndex:i] withArchivedObject:[defaultConsoleColors objectAtIndex: i]];
2219
		i++;
2220
	}
2221
	[[Preferences sharedPreferences] endBatch];
568 iacus 2222
}
2223
 
815 urbaneks 2224
- (void) updatePreferences {
1328 urbaneks 2225
	SLog(@"RController.updatePreferences");
815 urbaneks 2226
	currentFontSize = [Preferences floatForKey: FontSizeKey withDefault: 11.0];
898 urbaneks 2227
	NSFont *newFont = [NSFont userFixedPitchFontOfSize:currentFontSize];
2228
	if (newFont!=textFont) {
1328 urbaneks 2229
		SLog(@" - releasing %@", textFont);
898 urbaneks 2230
		[textFont release];
1328 urbaneks 2231
		SLog(@" - using new %@", textFont);
898 urbaneks 2232
		textFont = [newFont retain];
2233
	}
1603 urbaneks 2234
 
2235
	argsHints=[Preferences flagForKey:prefShowArgsHints withDefault:YES];
2236
 
815 urbaneks 2237
	{
2238
		int i = 0, ccs = [consoleColorsKeys count];
2239
		while (i<ccs) {
2240
			NSColor *c = [Preferences unarchivedObjectForKey: [consoleColorsKeys objectAtIndex:i] withDefault: [consoleColors objectAtIndex:i]];
2241
			if (c != [consoleColors objectAtIndex:i]) {
2242
				[consoleColors replaceObjectAtIndex:i withObject:c];
2243
				if (i == iBackgroundColor) {
2244
					[RConsoleWindow setBackgroundColor:c];
2245
					[RConsoleWindow display];
2246
				}
2247
			}
2248
			i++;
2249
		}
1489 urbaneks 2250
		[RTextView setInsertionPointColor:[consoleColors objectAtIndex:iInputColor]];
815 urbaneks 2251
	}
2252
	[RTextView setNeedsDisplay:YES];
1328 urbaneks 2253
	SLog(@" - done, preferences updated");
568 iacus 2254
}
2255
 
1490 urbaneks 2256
- (NSWindow*) window {
2257
	return RConsoleWindow;
2258
}
2259
 
638 iacus 2260
- (NSTextView *)getRTextView{
2261
	return RTextView;
2262
}
2263
 
2264
- (NSWindow *)getRConsoleWindow{
2265
	return RConsoleWindow;
2266
}
2267
 
1488 urbaneks 2268
- (void) setStatusLineText: (NSString*) text {
2269
	SLog(@"RController.setStatusLine: \"%@\"", text);
2270
	[statusLine setStringValue:text?text:@""];
2271
}
2272
 
1491 urbaneks 2273
- (NSString*) statusLineText {
2274
	return [statusLine stringValue];
2275
}
2276
 
568 iacus 2277
@end
2278
 
2279