The R Project SVN R-packages

Rev

Rev 5865 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
5853 bibiko 1
/*
2
 *  R.app : a Cocoa front end to: "R A Computer Language for Statistical Data Analysis"
3
 *  
4
 *  R.app Copyright notes:
5
 *                     Copyright (C) 2004-11  The R Foundation
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.
28
 *
29
 *  RDataEditorTableView.m
30
 *
31
 *  Created by Hans-J. Bibiko on 21/06/2011.
32
 *
33
 */
34
 
35
#import "RDataEditorTableView.h"
5854 bibiko 36
#import "RGUI.h"
5863 bibiko 37
#import "../REditor.h"
5865 bibiko 38
#import "../NSArray_RAdditions.h"
5853 bibiko 39
 
40
@implementation RDataEditorTableView
41
 
5855 bibiko 42
 
43
/**
44
 * Handles the general Copy action of selected rows as tab delimited data
45
 */
5853 bibiko 46
- (void)copy:(id)sender
47
{
5855 bibiko 48
	NSString *tmp = nil;
49
 
5863 bibiko 50
	switch([sender tag]) {
51
		case 0:
5868 bibiko 52
		tmp = ([self numberOfSelectedRows]) ? [self rowsAsTabStringWithHeaders:YES] : [self columnsAsTabStringWithHeaders:YES];
5863 bibiko 53
		break;
54
		case 1:
5868 bibiko 55
		tmp = ([self numberOfSelectedRows]) ? [self rowsAsCsvStringWithHeaders:YES] : [self columnsAsCsvStringWithHeaders:YES];
5863 bibiko 56
		break;
57
		default:
58
		NSBeep();
59
		return;
60
	}
61
 
5855 bibiko 62
	if ( nil != tmp )
63
	{
64
		NSPasteboard *pb = [NSPasteboard generalPasteboard];
65
 
66
		[pb declareTypes:[NSArray arrayWithObjects:
67
								NSTabularTextPboardType,
68
								NSStringPboardType,
69
								nil]
70
				   owner:nil];
71
 
72
		[pb setString:tmp forType:NSStringPboardType];
73
		[pb setString:tmp forType:NSTabularTextPboardType];
74
	}
75
 
5853 bibiko 76
}
77
 
78
- (NSUInteger)draggingSourceOperationMaskForLocal:(BOOL)isLocal
79
{
5868 bibiko 80
	return ([[self selectedRowIndexes] count]) ? NSDragOperationCopy : NSDragOperationNone;
5853 bibiko 81
}
82
 
83
- (NSString *)rowsAsTabStringWithHeaders:(BOOL)withHeaders
84
{
5855 bibiko 85
	if (![self numberOfSelectedRows]) return nil;
86
 
87
	NSIndexSet *selectedRows = [self selectedRowIndexes];
88
 
5858 bibiko 89
	NSUInteger i;
5855 bibiko 90
	NSArray *columns = [self tableColumns];
91
	NSUInteger numColumns = [columns count];
92
	NSMutableString *result = [NSMutableString stringWithCapacity:2000];
93
 
94
	// Add the table headers if requested to do so
95
	if (withHeaders) {
5865 bibiko 96
		for(i = 0; i < numColumns; i++ ){
5855 bibiko 97
			if([result length])
98
				[result appendString:@"\t"];
5865 bibiko 99
			[result appendString:[[NSArrayObjectAtIndex([self tableColumns], i) headerCell] title]];
5855 bibiko 100
		}
101
		[result appendString:@"\n"];
102
	}
103
 
104
	// Loop through the rows, adding their descriptive contents
105
	NSUInteger rowIndex = [selectedRows firstIndex];
5865 bibiko 106
	NSArray *ds = [[self delegate] objectData];
5855 bibiko 107
	while ( rowIndex != NSNotFound )
108
	{
5858 bibiko 109
		for ( i = 0; i < numColumns; i++ )
5865 bibiko 110
			[result appendFormat:@"%@\t", NSArrayObjectAtIndex(NSArrayObjectAtIndex(ds, i), rowIndex)];
5855 bibiko 111
 
112
		// Remove the trailing tab and add the linebreak
113
		if ([result length])
114
			[result deleteCharactersInRange:NSMakeRange([result length]-1, 1)];
115
 
116
		[result appendString:@"\n"];
117
 
118
		// Select the next row index
119
		rowIndex = [selectedRows indexGreaterThanIndex:rowIndex];
120
	}
121
 
122
	// Remove the trailing line end
123
	if ([result length]) {
124
		[result deleteCharactersInRange:NSMakeRange([result length]-1, 1)];
125
	}
126
 
127
	return result;
128
 
5853 bibiko 129
}
130
 
131
- (NSString *)rowsAsCsvStringWithHeaders:(BOOL)withHeaders
132
{
5858 bibiko 133
	if (![self numberOfSelectedRows]) return nil;
134
 
135
	NSIndexSet *selectedRows = [self selectedRowIndexes];
136
 
137
	NSUInteger i;
138
	NSArray *columns = [self tableColumns];
139
	NSUInteger numColumns = [columns count];
140
	NSMutableString *result = [NSMutableString stringWithCapacity:2000];
141
 
142
	// Add the table headers if requested to do so
143
	if (withHeaders) {
5865 bibiko 144
		for( i = 0; i < numColumns; i++ ){
5858 bibiko 145
			if([result length])
146
				[result appendString:@","];
5865 bibiko 147
			[result appendFormat:@"\"%@\"", [[[NSArrayObjectAtIndex([self tableColumns], i) headerCell] title] stringByReplacingOccurrencesOfString:@"\"" withString:@"\"\""]];
5858 bibiko 148
		}
149
		[result appendString:@"\n"];
150
	}
151
 
152
	// Loop through the rows, adding their descriptive contents
153
	NSUInteger rowIndex = [selectedRows firstIndex];
5865 bibiko 154
	NSArray *ds = [[self delegate] objectData];
155
	NSArray *columnTypes = [[self delegate] objectColumnTypes];
156
 
157
	NSInteger types[[columnTypes count]];
158
 
159
	for( i = 0; i < numColumns; i++ )
160
		types[i] = [NSArrayObjectAtIndex(columnTypes, i) intValue];
161
 
5858 bibiko 162
	while ( rowIndex != NSNotFound )
163
	{
5865 bibiko 164
 
5858 bibiko 165
		for ( i = 0; i < numColumns; i++ )
5865 bibiko 166
			if(types[i] == 1)
167
				[result appendFormat:@"%@,", NSArrayObjectAtIndex(NSArrayObjectAtIndex(ds, i), rowIndex)];
168
			else
169
				[result appendFormat:@"\"%@\",", [NSArrayObjectAtIndex(NSArrayObjectAtIndex(ds, i), rowIndex) stringByReplacingOccurrencesOfString:@"\"" withString:@"\"\""]];
5858 bibiko 170
 
171
		// Remove the trailing comma and add the linebreak
172
		if ([result length]){
173
			[result deleteCharactersInRange:NSMakeRange([result length]-1, 1)];
174
		}
175
		[result appendString:@"\n"];
176
 
177
		// Select the next row index
178
		rowIndex = [selectedRows indexGreaterThanIndex:rowIndex];
179
	}
180
 
181
	// Remove the trailing line end
182
	if ([result length]) {
183
		[result deleteCharactersInRange:NSMakeRange([result length]-1, 1)];
184
	}
185
 
186
	return result;
5853 bibiko 187
}
188
 
5868 bibiko 189
- (NSString *)columnsAsTabStringWithHeaders:(BOOL)withHeaders
190
{
191
	if (![self numberOfSelectedColumns]) return nil;
192
 
193
	NSIndexSet *selectedCols = [self selectedColumnIndexes];
194
	NSUInteger colIndex = [selectedCols firstIndex];
195
 
196
	NSUInteger i;
197
	NSMutableString *result = [NSMutableString stringWithCapacity:2000];
198
 
199
	// Add the table headers if requested to do so
200
	if (withHeaders) {
201
		while ( colIndex != NSNotFound ) {
202
			if([result length])
203
				[result appendString:@"\t"];
204
			[result appendString:[[NSArrayObjectAtIndex([self tableColumns], colIndex) headerCell] title]];
205
 
206
			// Select the next column index
207
			colIndex = [selectedCols indexGreaterThanIndex:colIndex];
208
		}
209
		[result appendString:@"\n"];
210
	}
211
 
212
	// Loop through the rows, adding their descriptive contents
213
	NSArray *ds = [[self delegate] objectData];
214
	for ( i = 0; i < [NSArrayObjectAtIndex(ds, 0) count]; i++ )
215
	{
216
		colIndex = [selectedCols firstIndex];
217
		while ( colIndex != NSNotFound ) {
218
			[result appendFormat:@"%@\t", NSArrayObjectAtIndex(NSArrayObjectAtIndex(ds, colIndex), i)];
219
			// Select the next column index
220
			colIndex = [selectedCols indexGreaterThanIndex:colIndex];
221
		}
222
 
223
		// Remove the trailing tab and add the linebreak
224
		if ([result length])
225
			[result deleteCharactersInRange:NSMakeRange([result length]-1, 1)];
226
 
227
		[result appendString:@"\n"];
228
 
229
	}
230
 
231
	// Remove the trailing line end
232
	if ([result length]) {
233
		[result deleteCharactersInRange:NSMakeRange([result length]-1, 1)];
234
	}
235
 
236
	return result;
237
 
238
}
239
 
240
- (NSString *)columnsAsCsvStringWithHeaders:(BOOL)withHeaders
241
{
242
	if (![self numberOfSelectedColumns]) return nil;
243
 
244
	NSIndexSet *selectedCols = [self selectedColumnIndexes];
245
	NSUInteger colIndex = [selectedCols firstIndex];
246
 
247
	NSUInteger i;
248
	NSMutableString *result = [NSMutableString stringWithCapacity:2000];
249
 
250
	// Add the table headers if requested to do so
251
	if (withHeaders) {
252
		while ( colIndex != NSNotFound ) {
253
			if([result length])
254
				[result appendString:@","];
255
 
256
			[result appendString:[NSString stringWithFormat:@"\"%@\"",[[[NSArrayObjectAtIndex([self tableColumns], colIndex) headerCell] title] stringByReplacingOccurrencesOfString:@"\"" withString:@"\"\""]]];
257
 
258
			// Select the next column index
259
			colIndex = [selectedCols indexGreaterThanIndex:colIndex];
260
		}
261
		[result appendString:@"\n"];
262
	}
263
 
264
	// Loop through the rows, adding their descriptive contents
265
	NSArray *ds = [[self delegate] objectData];
266
	NSArray *columnTypes = [[self delegate] objectColumnTypes];
267
 
268
	NSInteger types[[columnTypes count]];
269
 
270
	for( i = 0; i < [columnTypes count]; i++ )
271
		types[i] = [NSArrayObjectAtIndex(columnTypes, i) intValue];
272
 
273
	for ( i = 0; i < [NSArrayObjectAtIndex(ds, 0) count]; i++ )
274
	{
275
		colIndex = [selectedCols firstIndex];
276
		while ( colIndex != NSNotFound ) {
277
 
278
			if(types[colIndex] == 1)
279
				[result appendFormat:@"%@,", NSArrayObjectAtIndex(NSArrayObjectAtIndex(ds, colIndex), i)];
280
			else
281
				[result appendFormat:@"\"%@\",", [NSArrayObjectAtIndex(NSArrayObjectAtIndex(ds, colIndex), i) stringByReplacingOccurrencesOfString:@"\"" withString:@"\"\""]];
282
 
283
			// Select the next column index
284
			colIndex = [selectedCols indexGreaterThanIndex:colIndex];
285
		}
286
 
287
		// Remove the trailing comma and add the linebreak
288
		if ([result length]){
289
			[result deleteCharactersInRange:NSMakeRange([result length]-1, 1)];
290
		}
291
		[result appendString:@"\n"];
292
 
293
	}
294
 
295
	// Remove the trailing line end
296
	if ([result length]) {
297
		[result deleteCharactersInRange:NSMakeRange([result length]-1, 1)];
298
	}
299
 
300
	return result;
301
 
302
}
303
 
5854 bibiko 304
- (CGFloat)widthForColumn:(NSInteger)columnIndex andHeaderName:(NSString*)colName
5853 bibiko 305
{
306
 
5854 bibiko 307
	CGFloat        columnBaseWidth;
308
	NSString       *contentString;
309
	NSUInteger     cellWidth, maxCellWidth, i;
310
	NSRange        linebreakRange;
311
	double         rowStep;
312
 
313
	NSDictionary *stringAttributes = [NSDictionary dictionaryWithObject:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]
314
	 															 forKey:NSFontAttributeName];
315
 
316
	NSCharacterSet *newLineCharSet = [NSCharacterSet newlineCharacterSet];
317
 
318
	NSInteger rowsToCheck = 100;
319
	NSUInteger maxRows = [[self delegate] numberOfRowsInTableView:self];
320
 
321
	// Check the number of rows available to check, sampling every n rows
322
	if (maxRows < rowsToCheck)
323
		rowStep = 1;
324
	else
325
		rowStep = floor(maxRows / rowsToCheck);
326
 
327
	rowsToCheck = (rowsToCheck > maxRows) ? maxRows : rowsToCheck;
328
 
329
	// Set a default padding for this column
5863 bibiko 330
	columnBaseWidth = 32.0f;
5854 bibiko 331
 
332
	// Iterate through the data store rows, checking widths
5865 bibiko 333
	id ds = [[self delegate] objectData];
5854 bibiko 334
	maxCellWidth = 0;
5865 bibiko 335
	for (i = 0; i < maxRows; i += rowStep) {
5854 bibiko 336
 
5865 bibiko 337
		contentString = NSArrayObjectAtIndex(NSArrayObjectAtIndex(ds, columnIndex), i);
338
		if ([contentString length] > 500) {
5854 bibiko 339
			contentString = [contentString substringToIndex:500];
340
		}
341
 
342
		// If any linebreaks are present, use only the visible part of the string
343
		linebreakRange = [contentString rangeOfCharacterFromSet:newLineCharSet];
344
		if (linebreakRange.location != NSNotFound) {
345
			contentString = [contentString substringToIndex:linebreakRange.location];
346
		}
347
 
348
		// Calculate the width, using it if it's higher than the current stored width
349
		cellWidth = [contentString sizeWithAttributes:stringAttributes].width;
350
		if (cellWidth > maxCellWidth) maxCellWidth = cellWidth;
351
		if (maxCellWidth > 400) {
352
			maxCellWidth = 400;
353
			break;
354
		}
355
	}
356
 
357
	// Add the padding
358
	maxCellWidth += columnBaseWidth;
359
 
360
	// If the header width is wider than this expanded width, use it instead
361
	if(colName) {
362
		cellWidth = [colName sizeWithAttributes:[NSDictionary dictionaryWithObject:[NSFont labelFontOfSize:[NSFont smallSystemFontSize]] forKey:NSFontAttributeName]].width;
363
		if (cellWidth + columnBaseWidth > maxCellWidth) maxCellWidth = cellWidth + columnBaseWidth;
364
		if (maxCellWidth > 400) maxCellWidth = 400;
365
	}
366
 
367
	return maxCellWidth;
5853 bibiko 368
}
369
 
5863 bibiko 370
- (void)setFont:(NSFont *)font;
371
{
372
	NSArray *tableColumns = [self tableColumns];
373
	NSUInteger columnIndex = [tableColumns count];
374
 
375
	while (columnIndex--) 
376
	{
377
		[[(NSTableColumn *)[tableColumns objectAtIndex:columnIndex] dataCell] setFont:font];
378
	}
379
}
380
 
381
- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)command
382
{
383
 
384
	SLog(@"RDataEditorTableView received selector %@", NSStringFromSelector(command));
385
 
386
	NSInteger row, column;
387
 
388
	row = [self editedRow];
389
	column = [self editedColumn];
390
 
391
	// Trap down arrow key
392
	if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(moveDown:)] )
393
	{
394
 
395
		NSInteger newRow = row+1;
396
		if (newRow >= [[self delegate] numberOfRowsInTableView:self]) return YES; //check if we're already at the end of the list
397
 
398
		[[control window] makeFirstResponder:control];
399
 
400
		[self selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
401
		[self editColumn:column row:newRow withEvent:nil select:YES];
402
		return YES;
403
 
404
	}
405
 
406
	// Trap up arrow key
407
	else if ( [textView methodForSelector:command] == [textView methodForSelector:@selector(moveUp:)] )
408
	{
409
 
410
		if (row==0) return YES; //already at the beginning of the list
411
		NSInteger newRow = row-1;
412
 
413
		if (newRow>=[[self delegate] numberOfRowsInTableView:self]) return YES; // saveRowToTable could reload the table and change the number of rows
414
		[[control window] makeFirstResponder:control];
415
 
416
		[self selectRowIndexes:[NSIndexSet indexSetWithIndex:newRow] byExtendingSelection:NO];
417
		[self editColumn:column row:newRow withEvent:nil select:YES];
418
		return YES;
419
	}
420
 
421
	return NO;
422
 
423
}
424
 
5859 bibiko 425
- (void)keyDown:(NSEvent*)theEvent
426
{
427
	long allFlags = (NSShiftKeyMask|NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask);
428
	long curFlags = ([theEvent modifierFlags] & allFlags);
429
 
430
	// Check if user pressed ⌥ to allow composing of accented characters.
431
	// e.g. for US keyboard "⌥u a" to insert ä
432
	// or for non-US keyboards to allow to enter dead keys
433
	// e.g. for German keyboard ` is a dead key, press space to enter `
434
	if ((curFlags & allFlags) == NSAlternateKeyMask || [[theEvent characters] length] == 0) {
435
		[super keyDown:theEvent];
436
		return;
437
	}
438
 
439
	NSString *charactersIgnMod = [theEvent charactersIgnoringModifiers];
440
 
441
	// ⇧⌥⌘C - add col as CHARACTER
442
	if(((curFlags & allFlags) == (NSCommandKeyMask|NSAlternateKeyMask|NSShiftKeyMask)) && [charactersIgnMod isEqualToString:@"C"]) {
5863 bibiko 443
		NSMenuItem *m = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
444
		[m setTag:2];
445
		[[self delegate] addCol:m];
446
		[m release];
5859 bibiko 447
		return;
448
	}
449
 
450
	// ⌥⌘C - add col as NUMERIC
451
	if(((curFlags & allFlags) == (NSCommandKeyMask|NSAlternateKeyMask)) && [charactersIgnMod isEqualToString:@"c"]) {
5863 bibiko 452
		NSMenuItem *m = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
453
		[m setTag:1];
454
		[[self delegate] addCol:m];
455
		[m release];
5859 bibiko 456
		return;
457
	}
458
 
459
	// ⌥⌘A - add row
460
	if(((curFlags & allFlags) == (NSCommandKeyMask|NSAlternateKeyMask)) && [charactersIgnMod isEqualToString:@"a"]) {
461
		[[self delegate] addRow:nil];
462
		return;
463
	}
464
 
465
	// ^⌥⌘⎋ cancel editing without saving data
466
	if(((curFlags & allFlags) == (NSControlKeyMask|NSAlternateKeyMask|NSCommandKeyMask)) && [theEvent keyCode] == 51) {
467
		[[self delegate] cancelEditing:nil];
468
		return;
469
	}
470
 
471
	// ⌘⌫ delete selected rows or columns according to selection
472
	if((curFlags & allFlags) == NSCommandKeyMask && [theEvent keyCode] == 51) {
473
		if([[self selectedColumnIndexes] count])
474
			[[self delegate] remCols:nil];
475
		else if([[self selectedRowIndexes] count])
476
			[[self delegate] remRows:nil];
477
		else
478
			NSBeep();
479
		return;
480
	}
481
 
482
	[super keyDown:theEvent];
483
 
484
}
485
 
5855 bibiko 486
- (BOOL)validateMenuItem:(NSMenuItem*)menuItem
487
{
488
	if ([menuItem action] == @selector(copy:)) {
5868 bibiko 489
		return ([self numberOfSelectedRows] > 0 || [self numberOfSelectedColumns] > 0);
5855 bibiko 490
	}
5863 bibiko 491
	if ([menuItem action] == @selector(remSelection:)) {
492
		return ([[self selectedColumnIndexes] count] || [[self selectedRowIndexes] count]);
493
	}
5855 bibiko 494
 
495
	return YES;
496
 
497
}
5863 bibiko 498
 
5853 bibiko 499
@end