Rev 6455 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
/** R.app : a Cocoa front end to: "R A Computer Language for Statistical Data Analysis"** R.app Copyright notes:* Copyright (C) 2004-11 The R Foundation* written by Stefano M. Iacus and Simon Urbanek*** R Copyright notes:* Copyright (C) 1995-1996 Robert Gentleman and Ross Ihaka* Copyright (C) 1998-2001 The R Development Core Team* Copyright (C) 2002-2004 The R Foundation** This program is free software; you can redistribute it and/or modify* it under the terms of the GNU General Public License as published by* the Free Software Foundation; either version 2 of the License, or* (at your option) any later version.** This program is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the* GNU General Public License for more details.** A copy of the GNU General Public License is available via WWW at* http://www.gnu.org/copyleft/gpl.html. You can also obtain it by* writing to the Free Software Foundation, Inc., 59 Temple Place,* Suite 330, Boston, MA 02111-1307 USA.** REditor.m**/#include <R.h>#include <R_ext/Boolean.h>#include <R_ext/Rdynload.h>#include <Rinternals.h>#include <Rversion.h>void Rf_PrintDefaults(void);const char *Rf_EncodeElement(SEXP, int, int, char);#import "RGUI.h"#import "REditor.h"#import "Tools/RDataEditorTableHeaderCell.h"#import "NSArray_RAdditions.h"#ifndef max#define max(x,y) x<y?y:x;#endifstatic id sharedDEController;extern SEXP work, names, lens, ssNA_STRING;extern SEXP ssNewVector(SEXPTYPE type, int vlen);extern int xmaxused, ymaxused, nprotect;extern double ssNA_REAL;extern PROTECT_INDEX wpi, npi, lpi;typedef enum { UP, DOWN, LEFT, RIGHT } DE_DIRECTION;typedef enum { UNKNOWNN, NUMERIC, CHARACTER } CellType;int newvar;BOOL IsDataEntry;const char *get_col_name(int col){static char clab[25];if (col <= xmaxused) {// don't use NA labelsSEXP tmp = STRING_ELT(names, col);if(tmp != NA_STRING) return(CHAR(tmp));}sprintf(clab, "var%d", col);return clab;}void printelt(SEXP invec, int vrow, char *strp){if(!strp) return;Rf_PrintDefaults();if (TYPEOF(invec) == REALSXP) {if (REAL(invec)[vrow] != ssNA_REAL) {strcpy(strp, Rf_EncodeElement(invec, vrow, 0, '.'));return;}}else if (TYPEOF(invec) == STRSXP) {if(CHAR(STRING_ELT(invec, vrow))){if ( strcmp( CHAR(STRING_ELT(invec, vrow)),CHAR(STRING_ELT(ssNA_STRING, 0)) ) ) {strcpy(strp, Rf_EncodeElement(invec, vrow, 0, '.'));return;}}}elseerror("dataentry: internal memory error"); // FIXME: localize}@implementation REditor- (id)init{self = [super init];if (self) {if (!sharedDEController)sharedDEController = [self retain];// Add your subclass-specific initialization here.// If an error occurs here, send a [self release] message and return nil.toolbar = nil;editedColumnNameIndex = -1;objectData = [[NSMutableArray alloc] initWithCapacity:10];objectColumnNames = [[NSMutableArray alloc] initWithCapacity:10];objectColumnTypes = [[NSMutableArray alloc] initWithCapacity:10];numberOfColumns = 0;numberOfRows = 0;}return self;}- (void)awakeFromNib{[editorSource setColumnAutoresizingStyle:NSTableViewUniformColumnAutoresizingStyle];[self setupToolbar];// Since the window will be listed in the main menu > windows after closing// let it not listed there at all (and due to the fact that it's a modal window// it's not needed to have it there)[dataWindow setExcludedFromWindowsMenu:YES];}#pragma mark -+ (id)getDEController{return sharedDEController;}+ (void)startDataEntry{NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];newvar = 0;[[REditor getDEController] setDataTable:YES];[[[REditor getDEController] window] orderFront:self];NSInteger ret = [NSApp runModalForWindow:[[REditor getDEController] window]];// if modal session was aborted cancel edit()if( ret == NSRunAbortedResponse )error([NLS(@"editing cancelled") UTF8String]);[pool release];}#define NA_CHARS " <NA> "- (BOOL)writeDataBackToObject{NSInteger buflen = 256;NSInteger i, j;NSInteger nCols = [[editorSource tableColumns] count];NSInteger nRows = (nCols) ? [[objectData objectAtIndex:0] count] : 0;NSInteger colType;SEXP work3, names3;numberOfRows = 0;PROTECT(work3 = allocVector(VECSXP, nCols)); nprotect++;PROTECT(names3 = allocVector(STRSXP, nCols)); nprotect++;for(i = 0; i < nCols; i++) {SET_VECTOR_ELT(work3, i, ssNewVector(([NSArrayObjectAtIndex(objectColumnTypes, i) intValue] == NUMERIC) ? REALSXP : STRSXP, nRows));SEXP tmp = VECTOR_ELT(work3, i);SET_STRING_ELT(names3, i, mkChar([NSArrayObjectAtIndex(objectColumnNames, i) UTF8String]));colType = [NSArrayObjectAtIndex(objectColumnTypes, i) intValue];for(j = 0; j < nRows; j++) {NSString *anObject = NSArrayObjectAtIndex(NSArrayObjectAtIndex(objectData, i), j);buflen = 256;// get the number of utf-8 bytes for CHARACTER typeif(colType == CHARACTER)buflen = strlen([anObject UTF8String])+2;char buf[buflen];buf[0] = '\0';CFStringGetCString((CFStringRef)anObject, buf, buflen-1, kCFStringEncodingUTF8);switch(colType) {case NUMERIC:if(buf[0] == '\0')REAL(tmp)[j] = NA_REAL;else {char *endp;double new = R_strtod(buf, &endp);REAL(tmp)[j] = new;}break;case CHARACTER:// if(buf[0] == '\0')if(strcmp(buf, NA_CHARS) == 0)SET_STRING_ELT(tmp, j, NA_STRING);elseSET_STRING_ELT(tmp, j, mkChar(buf));break;default:return NO;break;}}}REPROTECT(work = allocVector(VECSXP, nCols), wpi);REPROTECT(names = allocVector(STRSXP, nCols), npi);xmaxused = nCols;for (i = 0; i < nCols; i++) {SET_VECTOR_ELT(work, i, VECTOR_ELT(work3, i));SET_STRING_ELT(names, i, STRING_ELT(names3, i));INTEGER(lens)[i] = nRows;}return YES;}- (BOOL)initData{numberOfRows = 0;numberOfColumns = 0;if(objectData) [objectData release], objectData = nil;if(objectColumnNames) [objectColumnNames release], objectColumnNames = nil;if(objectColumnTypes) [objectColumnTypes release], objectColumnTypes = nil;objectData = [[NSMutableArray alloc] initWithCapacity:xmaxused];objectColumnNames = [[NSMutableArray alloc] initWithCapacity:xmaxused];objectColumnTypes = [[NSMutableArray alloc] initWithCapacity:xmaxused];numberOfRows = ymaxused;NSInteger i;NSNumber *charNumber = [NSNumber numberWithInt:CHARACTER];NSNumber *numNumber = [NSNumber numberWithInt:NUMERIC];for(i = 0; i < xmaxused; i++) {SEXP tmp = VECTOR_ELT(work, i);if (isNull(tmp)) {NSBeep();error([NLS(@"R Data Editor - error while reading object data") UTF8String]);return NO;break;}CFArrayAppendValue((CFMutableArrayRef)objectColumnTypes, (TYPEOF(tmp) == STRSXP) ? charNumber : numNumber);CFArrayAppendValue((CFMutableArrayRef)objectColumnNames, CFStringCreateWithCString(NULL, get_col_name(i), kCFStringEncodingUTF8));if(TYPEOF(tmp) == STRSXP) {NSUInteger k=0, l=LENGTH(tmp);id *cont=(id *)malloc(sizeof(id)*l);while (k<l) {if ( strcmp( CHAR(STRING_ELT(tmp, k)), CHAR(STRING_ELT(ssNA_STRING, 0)) ) )cont[k] = (NSString*)CFStringCreateWithCString(NULL, CHAR(STRING_ELT(tmp, k)), kCFStringEncodingUTF8);elsecont[k] = @NA_CHARS;k++;}NSArray *a = [NSArray arrayWithObjects:cont count:l];k=0;while (k<l) [cont[k++] release];free(cont);CFArrayAppendValue((CFMutableArrayRef)objectData, [a mutableCopy]);}else if(TYPEOF(tmp) == REALSXP){NSUInteger k=0, l=LENGTH(tmp);id *cont = malloc(sizeof(id)*l);while (k<l) {cont[k] = (NSString*)CFStringCreateWithCString(NULL, Rf_EncodeElement(tmp, k, 0, '.'), kCFStringEncodingASCII);k++;}NSArray *a = [NSArray arrayWithObjects:cont count:l];k=0;while (k<l) [cont[k++] release];free(cont);CFArrayAppendValue((CFMutableArrayRef)objectData, [a mutableCopy]);}else {NSBeep();error([NLS(@"R Data Editor - error while reading object data") UTF8String]);return NO;}}numberOfColumns = xmaxused;return YES;}- (void)setDataTable:(BOOL)removeAll{if(removeAll)if(![self initData]) return;NSInteger i;NSArray *theColumns = [editorSource tableColumns];while ([theColumns count])[editorSource removeTableColumn:NSArrayObjectAtIndex(theColumns ,0)];if(!objectData) return;for (i = 0; i < numberOfColumns; i++) {NSTableColumn *col = [[NSTableColumn alloc] initWithIdentifier:[NSString stringWithFormat:@"%ld", i]];NSString *colName = NSArrayObjectAtIndex(objectColumnNames, i);NSInteger colType = [NSArrayObjectAtIndex(objectColumnTypes, i) intValue];if(colName) {RDataEditorTableHeaderCell *c = [[RDataEditorTableHeaderCell alloc] initTextCell:colName];[col setHeaderCell:c];[c release];[[col headerCell] setAlignment:NSCenterTextAlignment];[col setHeaderToolTip:[NSString stringWithFormat:@"%@\n (%@)", colName,(colType == NUMERIC) ? @"numeric" : @"character"]];}[col setResizingMask:NSTableColumnUserResizingMask];[col setEditable:YES];// set text cell alignment to 'right' for numeric valuesif(colType == NUMERIC) [[col dataCell] setAlignment:NSRightTextAlignment];[col setMinWidth:18.0f];[col setMaxWidth:1000.0f];[editorSource addTableColumn:col];[col release];}// column auto-sizingfor(i = 0; i < numberOfColumns; i++) {[[editorSource tableColumnWithIdentifier:[NSString stringWithFormat:@"%ld", i]] setWidth:[editorSource widthForColumn:i andHeaderName:NSArrayObjectAtIndex(objectColumnNames, i)]];if(i+1 < numberOfColumns)[[[[editorSource tableColumnWithIdentifier:[NSString stringWithFormat:@"%ld", i]] headerCell] controlView] setNextKeyView:[[[editorSource tableColumnWithIdentifier:[NSString stringWithFormat:@"%ld", i+1]] headerCell] controlView]];else if(i-1 == numberOfColumns)[[[[editorSource tableColumnWithIdentifier:[NSString stringWithFormat:@"%ld", i]] headerCell] controlView] setNextKeyView:editorSource];}[editorSource sizeLastColumnToFit];//tries to fix problem with last rowif ( [[editorSource tableColumnWithIdentifier:[NSString stringWithFormat:@"%ld", numberOfColumns-1]] width] < 30 )[[editorSource tableColumnWithIdentifier:[NSString stringWithFormat:@"%ld", numberOfColumns-1]]setWidth:[[editorSource tableColumnWithIdentifier:[NSString stringWithFormat:@"%ld", 0L]] width]];[editorSource reloadData];}- (void)editColumnNameOfTableColumn:(NSTableColumn *)aTableColumn{RDataEditorTableHeaderCell *headerCellEditor = [aTableColumn headerCell];NSText *editor = [[[REditor getDEController] window] fieldEditor:YES forObject:headerCellEditor];editedColumnNameIndex = [[aTableColumn identifier] intValue];[editor setFieldEditor:YES];NSRect r = [[editorSource headerView] headerRectOfColumn:editedColumnNameIndex];r = NSInsetRect(r, 0.0f, 1.0f);[headerCellEditor editWithFrame: rinView:[editorSource headerView]editor:editordelegate:selfevent:nil];[headerCellEditor hideTitle];[editor selectAll:nil];}- (NSArray*)objectData{return objectData;}- (NSArray*)objectColumnTypes{return objectColumnTypes;}- (void)clearData{[objectColumnTypes removeAllObjects];[objectColumnNames removeAllObjects];[objectData removeAllObjects];}- (void)dealloc{if(objectColumnTypes) [objectColumnTypes release];if(objectColumnNames) [objectColumnNames release];if(objectData) [objectData release];[super dealloc];}#pragma mark -- (id)window{return dataWindow;}#pragma mark -#pragma mark NSWindow delegates- (BOOL)windowShouldClose:(id)sender{// Make sure that any pending changes will be stored before closingif(editedColumnNameIndex > -1)[self textView:(NSTextView*)[[NSApp keyWindow] firstResponder] doCommandBySelector:@selector(insertNewline:)];[[[REditor getDEController] window] makeFirstResponder:editorSource];if(![self writeDataBackToObject]) {error([NLS(@"R Data Editor couldn't write object data") UTF8String]);}if(IsDataEntry){[NSApp stopModal];IsDataEntry = NO;}return YES;}#pragma mark -#pragma mark NSTableView delegates- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView{return numberOfRows;}- (void)tableViewSelectionIsChanging:(NSNotification *)aNotification{if([[[NSApp keyWindow] firstResponder] isKindOfClass:[NSTableView class]] && editedColumnNameIndex > -1) {// Submit pending changes[self textView:(NSTextView*)[[NSApp keyWindow] firstResponder] doCommandBySelector:@selector(insertNewline:)];editedColumnNameIndex = -1;}}- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row{if(row < 0) return @"…";int col = (int) [[tableColumn identifier] intValue];if (col >= [objectData count]) return @"…";NSArray *a = NSArrayObjectAtIndex(objectData, col);if (!a || row >= [a count]) return @"…";return NSArrayObjectAtIndex(a, row);}- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row{if(row < 0) return;NSInteger col = [[tableColumn identifier] intValue];if([[objectColumnTypes objectAtIndex:col] intValue] == NUMERIC) {// validate NUMERIC valuechar *endp;(void)R_strtod([anObject UTF8String], &endp);if(strlen(endp))[[objectData objectAtIndex:col] replaceObjectAtIndex:row withObject:@"NA"];else {[[objectData objectAtIndex:col] replaceObjectAtIndex:row withObject:anObject];}} else {[[objectData objectAtIndex:col] replaceObjectAtIndex:row withObject:anObject];}// resize column widthCGFloat newSize = [editorSource widthForColumn:col andHeaderName:(NSString*)anObject];if(newSize > [tableColumn width]) [tableColumn setWidth:newSize];return;}/*** Enable drag from tableview*/- (BOOL)tableView:(NSTableView *)aTableView writeRowsWithIndexes:(NSIndexSet *)rows toPasteboard:(NSPasteboard*)pboard{if (aTableView == editorSource) {NSString *tmp;// By holding ⌘ or/and ⌥ copies selected rows as CSV// otherwise \t delimited linesif([[NSApp currentEvent] modifierFlags] & (NSCommandKeyMask|NSAlternateKeyMask))tmp = [editorSource rowsAsCsvStringWithHeaders:YES];elsetmp = [editorSource rowsAsTabStringWithHeaders:YES];if ( nil != tmp && [tmp length] ){[pboard declareTypes:[NSArray arrayWithObjects:NSTabularTextPboardType,NSStringPboardType, nil]owner:nil];[pboard setString:tmp forType:NSStringPboardType];[pboard setString:tmp forType:NSTabularTextPboardType];return YES;}}return NO;}- (void)tableView:(NSTableView *)tableView mouseDownInHeaderOfTableColumn:(NSTableColumn *)tableColumn{// Go into column name editing mode if user did a double-click at the column's headerif([[NSApp currentEvent] clickCount] > 1) {[self editColumnNameOfTableColumn:tableColumn];}}#pragma mark -#pragma mark NSText delegates- (BOOL)textView:(NSTextView *)aTextView doCommandBySelector:(SEL)commandSelector{SLog(@"REditor:textView:doCommandBySelector: %@ %@", [aTextView class], NSStringFromSelector(commandSelector));if(editedColumnNameIndex < 0) return NO;// Trap ESC while editing column names to cancel editingif (@selector(cancelOperation:) == commandSelector) {NSTableColumn *col = [editorSource tableColumnWithIdentifier:[NSString stringWithFormat:@"%ld", editedColumnNameIndex]];[[col headerCell] setTitle:[[col headerCell] titleBeforeEditing]];editedColumnNameIndex = -1;[[[[REditor getDEController] window] fieldEditor:NO forObject:[col headerCell]] setFieldEditor:YES];[[[REditor getDEController] window] makeFirstResponder:editorSource];[[[REditor getDEController] window] endEditingFor:[col headerCell]];[[editorSource headerView] setNeedsDisplay:YES];return YES;}// Trap RETURN, TAB, SHIFT+TAB while editing column names to end editing and// storing the new column name after validation; in addition for TABs edit// the next/previous column name if possibleif ( @selector(insertNewline:) == commandSelector|| @selector(insertTab:) == commandSelector|| @selector(insertBacktab:) == commandSelector) {NSTableColumn *col = [editorSource tableColumnWithIdentifier:[NSString stringWithFormat:@"%ld", editedColumnNameIndex]];if(![[[REditor getDEController] window] fieldEditor:NO forObject:[col headerCell]]) return NO;NSString *newColumnName = [[[[[REditor getDEController] window] fieldEditor:NO forObject:[col headerCell]] string] copy];// validate newColumnNameBOOL newNameIsValid = YES;if(!newColumnName || ![newColumnName length]) newNameIsValid = NO;if(newNameIsValid) {NSArray *allCols = [editorSource tableColumns];NSInteger i;for(i = 0; i < [allCols count]; i++) {if([[[[allCols objectAtIndex:i] headerCell] title] isEqualToString:newColumnName]) {newNameIsValid = NO;break;}}}if(newNameIsValid) {[[col headerCell] setTitle:newColumnName];[col setHeaderToolTip:[NSString stringWithFormat:@"%@\n (%@)", newColumnName,([[objectColumnTypes objectAtIndex:editedColumnNameIndex] intValue] == NUMERIC) ? @"numeric" : @"character"]];[objectColumnNames replaceObjectAtIndex:[[col identifier] intValue] withObject:newColumnName];} else {[[col headerCell] setTitle:[[col headerCell] titleBeforeEditing]];if([aTextView isKindOfClass:[NSTextView class]] && ![[[col headerCell] title] isEqualToString:newColumnName])NSBeep();}[[[[REditor getDEController] window] fieldEditor:NO forObject:[col headerCell]] setFieldEditor:YES];[[[REditor getDEController] window] makeFirstResponder:editorSource];[[[REditor getDEController] window] endEditingFor:[col headerCell]];[[[col headerCell] controlView] display];[[editorSource headerView] display];[newColumnName release];// TAB select next column name if anyif(@selector(insertTab:) == commandSelector) {if(editedColumnNameIndex + 1 < [[editorSource tableColumns] count]) {NSInteger i = editedColumnNameIndex;editedColumnNameIndex = -1;[self editColumnNameOfTableColumn:[[editorSource tableColumns] objectAtIndex:i+1]];return YES;}}// SHIFT+TAB select previous column name if anyelse if(@selector(insertBacktab:) == commandSelector) {if(editedColumnNameIndex > 0) {NSInteger i = editedColumnNameIndex;editedColumnNameIndex = -1;[self editColumnNameOfTableColumn:[[editorSource tableColumns] objectAtIndex:i-1]];return YES;}}editedColumnNameIndex = -1;return (![aTextView isKindOfClass:[NSTextView class]]);}return NO;}/*** Trap ESC,⇡, and ⇣ inside TableView*/- (BOOL)control:(NSControl*)control textView:(NSTextView*)aTextView doCommandBySelector:(SEL)command{if([control isKindOfClass:[RDataEditorTableView class]]) {// Check firstly if RDataEditorTableView can handle command (handle ⇡ ⇣)if([editorSource control:control textView:aTextView doCommandBySelector:(SEL)command])return YES;// Trap the escape key to abort editingif ([[control window] methodForSelector:command] == [[control window] methodForSelector:@selector(cancelOperation:)]){// Abort editing[control abortEditing];[[[REditor getDEController] window] makeFirstResponder:editorSource];return YES;}}return NO;}#pragma mark -#pragma mark Toolbar Methods- (void)setupToolbar{// Create a new toolbar instance, and attach it to our document windowtoolbar = [[NSToolbar alloc] initWithIdentifier:DataEditorToolbarIdentifier];// Set up toolbar properties: Allow customization, give a default display mode, and remember state in user defaults[toolbar setAllowsUserCustomization:YES];[toolbar setAutosavesConfiguration:YES];[toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];// We are the delegate[toolbar setDelegate:self];// Attach the toolbar to the document window[dataWindow setToolbar:toolbar];}- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdent willBeInsertedIntoToolbar:(BOOL)willBeInserted{// Required delegate method: Given an item identifier, this method returns an item// The toolbar will use this method to obtain toolbar items that can be displayed in the customization sheet, or in the toolbar itself// NSLog(@"toolbar: %@ itemForItemIdentifier:%@ willBeInsertedIntoToolbar:%d\n", toolbar, itemIdent, willBeInserted);NSToolbarItem *toolbarItem = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdent] autorelease];if ([itemIdent isEqual:AddColToolbarItemIdentifier]) {// Set the text label to be displayed in the toolbar and customization palette[toolbarItem setLabel:NLSC(@"Add Col", @"Add column - label for a toolbar, keep short!")];[toolbarItem setPaletteLabel:NLS(@"Add Column")];// Set up a reasonable tooltip, and image Note, these aren't localized, but you will likely want to localize many of the item's properties[toolbarItem setToolTip:[NSString stringWithFormat:NLS(@"Adds a new column to the right of a group of selected columns or just at the end of the data. The new column type complies with that column left of it.\n\n(%@)\tAdd column of type ‘CHARACTER’\n(%@)\tAdd column of type ‘NUMERIC’"), @"⇧⌥⌘C", @"⌥⌘C"]];[toolbarItem setImage:[NSImage imageNamed:@"add_col"]];// Tell the item what message to send when it is clicked[toolbarItem setTarget:self];[toolbarItem setAction:@selector(addCol:)];} else if ([itemIdent isEqual:RemoveColsToolbarItemIdentifier]) {// Set the text label to be displayed in the toolbar and customization palette[toolbarItem setLabel:NLSC(@"Remove Col",v@"Remove columns - label for a toolbar, keep short!")];[toolbarItem setPaletteLabel:NLS(@"Remove Columns")];// Set up a reasonable tooltip, and image Note, these aren't localized, but you will likely want to localize many of the item's properties[toolbarItem setToolTip:[NSString stringWithFormat:@"%@ (⌘⌫)", NLS(@"Remove selected columns")]];[toolbarItem setImage:[NSImage imageNamed:@"rem_col"]];// Tell the item what message to send when it is clicked[toolbarItem setTarget:self];[toolbarItem setAction:@selector(remCols:)];} else if ([itemIdent isEqual:AddRowToolbarItemIdentifier]) {// Set the text label to be displayed in the toolbar and customization palette[toolbarItem setLabel:NLSC(@"Add Row", @"Add row - label for a toolbar, keep short!")];[toolbarItem setPaletteLabel:NLS(@"Add New Row")];// Set up a reasonable tooltip, and image Note, these aren't localized, but you will likely want to localize many of the item's properties[toolbarItem setToolTip:[NSString stringWithFormat:@"%@ (⌥⌘A)", NLS(@"Adds a row below a group of selected rows or at the bottom of the data")]];[toolbarItem setImage:[NSImage imageNamed:@"add_row"]];// Tell the item what message to send when it is clicked[toolbarItem setTarget:self];[toolbarItem setAction:@selector(addRow:)];} else if ([itemIdent isEqual:RemoveRowsToolbarItemIdentifier]) {// Set the text label to be displayed in the toolbar and customization palette[toolbarItem setLabel:NLSC(@"Remove Row", @"Remove row - label for a toolbar, keep short!")];[toolbarItem setPaletteLabel:NLS(@"Remove Rows")];// Set up a reasonable tooltip, and image Note, these aren't localized, but you will likely want to localize many of the item's properties[toolbarItem setToolTip:[NSString stringWithFormat:@"%@ (⌘⌫)", NLS(@"Removes selected rows")]];[toolbarItem setImage:[NSImage imageNamed:@"rem_row"]];// Tell the item what message to send when it is clicked[toolbarItem setTarget:self];[toolbarItem setAction:@selector(remRows:)];} else if ([itemIdent isEqual:CancelEditToolbarItemIdentifier]) {// Set the text label to be displayed in the toolbar and customization palette[toolbarItem setLabel:NLSC(@"Cancel Editing", @"Cancel Editing - label for a toolbar, keep short!")];[toolbarItem setPaletteLabel:NLS(@"Cancel Editing")];// Set up a reasonable tooltip, and image Note, these aren't localized, but you will likely want to localize many of the item's properties[toolbarItem setToolTip:[NSString stringWithFormat:@"%@ (⌃⌥⌘⎋)", NLS(@"Cancels object editing without passing data back to R and closes the editor window")]];[toolbarItem setImage:[NSImage imageNamed:@"stop"]];// Tell the item what message to send when it is clicked[toolbarItem setTarget:self];[toolbarItem setAction:@selector(cancelEditing:)];} else {// itemIdent refered to a toolbar item that is not provide or supported by us or cocoa// Returning nil will inform the toolbar this kind of item is not supportedtoolbarItem = nil;}return toolbarItem;}- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar{// Required delegate method: Returns the ordered list of items to be shown in the toolbar by default// If during the toolbar's initialization, no overriding values are found in the user defaults, or if the// user chooses to revert to the default items this set will be usedreturn [NSArray arrayWithObjects:AddColToolbarItemIdentifier, RemoveColsToolbarItemIdentifier,AddRowToolbarItemIdentifier, RemoveRowsToolbarItemIdentifier, CancelEditToolbarItemIdentifier, nil];}- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar{// Required delegate method: Returns the list of all allowed items by identifier. By default, the toolbar// does not assume any items are allowed, even the separator. So, every allowed item must be explicitly listed// The set of allowed items is used to construct the customization palettereturn [NSArray arrayWithObjects:AddColToolbarItemIdentifier, RemoveColsToolbarItemIdentifier,AddRowToolbarItemIdentifier, RemoveRowsToolbarItemIdentifier, CancelEditToolbarItemIdentifier, nil];}- (void)toolbarWillAddItem:(NSNotification *)notification{// Optional delegate method: Before an new item is added to the toolbar, this notification is posted.// This is the best place to notice a new item is going into the toolbar. For instance, if you need to// cache a reference to the toolbar item or need to set up some initial state, this is the best place// to do it. The notification object is the toolbar to which the item is being added. The item being// added is found by referencing the @"item" key in the userInfo//NSToolbarItem *addedItem = [[notif userInfo] objectForKey: @"item"];//NSLog(@"toolbarWillAddItem: %@", addedItem);}- (void)toolbarDidRemoveItem:(NSNotification *)notification{// Optional delegate method: After an item is removed from a toolbar, this notification is sent. This allows// the chance to tear down information related to the item that may have been cached. The notification object// is the toolbar from which the item is being removed. The item being added is found by referencing the @"item"// key in the userInfo//NSToolbarItem *removedItem = [[notif userInfo] objectForKey: @"item"];//NSLog(@"toolbarDidRemoveItem: %@", removedItem);}- (BOOL)validateToolbarItem:(NSToolbarItem *)toolbarItem{// Optional method: This message is sent to us since we are the target of some toolbar item actions// (for example: of the save items action)if ([[toolbarItem itemIdentifier] isEqualToString:AddColToolbarItemIdentifier])return(editedColumnNameIndex < 0);else if ([[toolbarItem itemIdentifier] isEqualToString:RemoveColsToolbarItemIdentifier])return(editedColumnNameIndex < 0 && [[editorSource selectedColumnIndexes] count]);else if ([[toolbarItem itemIdentifier] isEqualToString:AddRowToolbarItemIdentifier])return(editedColumnNameIndex < 0);else if ([[toolbarItem itemIdentifier] isEqualToString:RemoveRowsToolbarItemIdentifier])return(editedColumnNameIndex < 0 && [[editorSource selectedRowIndexes] count]);else if ([[toolbarItem itemIdentifier] isEqualToString:CancelEditToolbarItemIdentifier])return(YES);return NO;}#pragma mark -#pragma mark IBActions/*** Adds a column to the data either at the end or after the last columns selected by the user.* sender's tag will choose column type* 0 = type of column left of it* 1 = CHARACTER* 2 = NUMERIC*/- (IBAction)addCol:(id)sender{NSUInteger lastcol, i;BOOL isEmpty = NO;BOOL typeWasPassed = NO;NSInteger newColType = CHARACTER;// if sender's tag pre-set type of to be added columnsswitch([sender tag]) {case 1:newColType = NUMERIC;typeWasPassed = YES;break;case 2:newColType = CHARACTER;typeWasPassed = YES;break;}NSIndexSet *cols = [editorSource selectedColumnIndexes];NSInteger nCols = [[editorSource tableColumns] count];lastcol = [cols lastIndex];if(lastcol == NSNotFound) {isEmpty = (nCols == 0);lastcol = !isEmpty ? (nCols - 1) : 0;}// add a column of type of last selected column or lastif(!typeWasPassed && !isEmpty)newColType = [[objectColumnTypes objectAtIndex:lastcol] intValue];newvar++;if((nCols-1) == lastcol) {[objectColumnNames addObject:[NSString stringWithFormat:@"var %d", newvar]];[objectColumnTypes addObject:[NSNumber numberWithInteger:newColType]];[objectData addObject:[NSMutableArray array]];} else {[objectColumnNames insertObject:[NSString stringWithFormat:@"var %d", newvar] atIndex:lastcol+1];[objectColumnTypes insertObject:[NSNumber numberWithInteger:newColType] atIndex:lastcol+1];[objectData insertObject:[NSMutableArray array] atIndex:lastcol+1];}numberOfColumns++;if(!isEmpty)for(i = 0; i < numberOfRows; i++)[NSArrayObjectAtIndex(objectData, (lastcol+1)) addObject:(newColType == NUMERIC) ? @"NA" : @""];[self setDataTable:NO];}/*** Adds a row to the data either at the end or after the last row selected by the user** FIXME: it actually crashes if a row is added in the middle of the data* Bibiko: cannot reproduce anymore; still valid?**/- (IBAction)addRow:(id)sender{NSUInteger col, lastrow;BOOL isEmpty = NO;if(![[editorSource tableColumns] count]) return;NSIndexSet *rows = [editorSource selectedRowIndexes];NSInteger nRows = ([objectData count]) ? [[objectData objectAtIndex:0] count] : 0;lastrow = [rows lastIndex]; // last row selected by the userif(lastrow == NSNotFound) {isEmpty = (nRows == 0);lastrow = isEmpty ? 0 : (nRows - 1);}if(lastrow == nRows)for(col = 0; col < [[editorSource tableColumns] count]; col++)[[objectData objectAtIndex:col] addObject:([[objectColumnTypes objectAtIndex:col] intValue] == NUMERIC) ? @"NA" : @""];elsefor(col = 0; col < [[editorSource tableColumns] count]; col++)[[objectData objectAtIndex:col] insertObject:([[objectColumnTypes objectAtIndex:col] intValue] == NUMERIC) ? @"NA" : @"" atIndex:lastrow+1];numberOfRows++;[editorSource reloadData];if([rows count] == 1)[editorSource selectRowIndexes:[NSIndexSet indexSetWithIndex:lastrow+1] byExtendingSelection:NO];return;}/*** Remove selected columns*/- (IBAction)remCols:(id)sender{NSUInteger i;NSIndexSet *cols = [editorSource selectedColumnIndexes];NSUInteger current_index = [cols firstIndex];if(current_index == NSNotFound)return;while (current_index != NSNotFound) {[editorSource removeTableColumn:[editorSource tableColumnWithIdentifier:[NSString stringWithFormat:@"%ld", current_index]]];current_index = [cols indexGreaterThanIndex:current_index];}[objectData removeObjectsAtIndexes:cols];[objectColumnTypes removeObjectsAtIndexes:cols];[objectColumnNames removeObjectsAtIndexes:cols];numberOfColumns -= [cols count];// if no column is given create a matrix 1 x 1 of value NAif(!numberOfColumns) {[objectData addObject:[NSMutableArray array]];[[objectData objectAtIndex:0] addObject:@"NA"];[objectColumnNames addObject:@"var1"];[objectColumnTypes addObject:[NSNumber numberWithInteger:NUMERIC]];numberOfRows = 1;numberOfColumns++;[self setDataTable:NO];} else {for(i = 0; i < numberOfColumns; i++)[[[editorSource tableColumns] objectAtIndex:i] setIdentifier:[NSString stringWithFormat:@"%ld", i]];[editorSource reloadData];[editorSource selectColumnIndexes:[NSIndexSet indexSetWithIndex:([cols lastIndex] < [[editorSource tableColumns] count]) ? [cols lastIndex] : [[editorSource tableColumns] count]-1] byExtendingSelection:NO];}}/*** Removes selected rows*/- (IBAction)remRows:(id)sender{NSUInteger col, nrows;NSIndexSet *rows = [editorSource selectedRowIndexes];nrows = [rows count];if (nrows < 1) return;numberOfRows -= nrows;for(col = 0; col < numberOfColumns; col++)[[objectData objectAtIndex:col] removeObjectsAtIndexes:rows];[editorSource reloadData];// Check last selected rows to reset selection if user deleted last row[editorSource selectRowIndexes:[NSIndexSet indexSetWithIndex:([rows firstIndex] < numberOfRows) ? [rows firstIndex] : numberOfRows-1] byExtendingSelection:NO];}- (IBAction)remSelection:(id)sender{if([[editorSource selectedColumnIndexes] count])[self remCols:nil];else if([[editorSource selectedRowIndexes] count])[self remRows:nil];}- (IBAction)editColumnNames:(id)sender{if([[editorSource tableColumns] count])[self editColumnNameOfTableColumn:[[editorSource tableColumns] objectAtIndex:0]];elseNSBeep();}/*** Cancel editing without storing data and close the window*/- (IBAction)cancelEditing:(id)sender{// sending abort signal to startDataEntry's runModalForWindow// to cancel the edit() by sending an error() message back to R[NSApp abortModal];[[[REditor getDEController] window] orderOut:self];[[[REditor getDEController] window] close];}#pragma mark -- (BOOL)validateMenuItem:(NSMenuItem*)menuItem{if ([menuItem action] == @selector(remSelection:)) {return ([[editorSource selectedColumnIndexes] count] || [[editorSource selectedRowIndexes] count]);}if ([menuItem action] == @selector(editColumnNames:)) {return ([[editorSource tableColumns] count] && editedColumnNameIndex < 0);}return YES;}@end