// // NumericalArrayDS.h // // Numerical array data sources for NSTableView. Simplifies the binding of // a simple array of numbers (integers and floating point types) to a // Cocoa table. // // This source code is provided as-is, and I provide no warranty as to its // functioning. That being said, I have done a bit of testing with it and // it seems to work fine. // // No explicit licensing is attached to this source code, I merely ask // that you let me know if it was useful to you in your project, and if you // would just make mention in the documentation somewhere that you used // this work. // // Created by Jeffrey Frey on 28 Jan 2007 // Copyright (c) 2007 // #import "NumericalArrayDS.h" NSString* NumericalArrayDSCellTemplateKey = @"CellTemplate"; NSString* NumericalArrayDSFontKey = @"Font"; NSString* NumericalArrayDSFormatterKey = @"Formatter"; NSString* NumericalArrayDSInitialColumnWidthKey = @"InitialColumnWidth"; NSString* NumericalArrayDSMinimumColumnWidthKey = @"MinimumColumnWidth"; NSString* NumericalArrayDSMaximumColumnWidthKey = @"MaximumColumnWidth"; // @interface NumericalArray(NumericalArrayPrivate) - (long) arrayIndexForRow:(int)row andColumn:(int)column; @end @implementation NumericalArray(NumericalArrayPrivate) - (long) arrayIndexForRow:(int)row andColumn:(int)column { if ( _orientation == NumericalArrayOrientationRowMajor ) return (row * _columns + column); return (column * _rows + row); } @end // #pragma mark - // @implementation NumericalArray - (id) initWithArrayType:(NumericalArrayType)type array:(void*)array rows:(int)rows columns:(int)columns { return [self initWithArrayType:type array:array rows:rows columns:columns orientation:NumericalArrayOrientationColumnMajor freeWhenDone:NO]; } // - (id) initWithArrayType:(NumericalArrayType)type array:(void*)array rows:(int)rows columns:(int)columns orientation:(NumericalArrayOrientation)orientation { return [self initWithArrayType:type array:array rows:rows columns:columns orientation:orientation freeWhenDone:NO]; } // - (id) initWithArrayType:(NumericalArrayType)type array:(void*)array rows:(int)rows columns:(int)columns orientation:(NumericalArrayOrientation)orientation freeWhenDone:(BOOL)fwd { if ( self = [super init] ) { if ( ((type >= 0) && (type < NumericalArrayTypeMax)) && ((orientation == NumericalArrayOrientationColumnMajor) || (orientation == NumericalArrayOrientationRowMajor)) && (rows > 0) && (columns > 0) ) { _arrayType = type; _orientation = orientation; _rows = rows; _columns = columns; if ( array ) { _array = array; _freeWhenDone = fwd; } else { size_t bytes = rows * columns; switch ( type ) { case NumericalArrayTypeInt: bytes *= sizeof(int); break; case NumericalArrayTypeLong: bytes *= sizeof(long); break; case NumericalArrayTypeLongLong: bytes *= sizeof(long long); break; case NumericalArrayTypeFloat: bytes *= sizeof(float); break; case NumericalArrayTypeDouble: bytes *= sizeof(double); break; } _array = calloc(1,bytes); if ( _array == NULL ) { [self release]; self = nil; } else { _freeWhenDone = YES; } } } else { [self release]; self = nil; } } return self; } // - (void) dealloc { if ( _freeWhenDone ) free(_array); [super dealloc]; } // - (NumericalArrayType) arrayType { return _arrayType; } - (NumericalArrayOrientation) orientation { return _orientation; } - (void*) array { return _array; } // - (int) rows { return _rows; } - (int) columns { return _columns; } // - (NSNumberFormatter*) defaultFormatter { NSNumberFormatter* theFormatter = [[[NSNumberFormatter alloc] init] autorelease]; #if MAC_OS_X_VERSION_10_4 <= MAC_OS_X_VERSION_MAX_ALLOWED switch ( _arrayType ) { case NumericalArrayTypeInt: case NumericalArrayTypeLong: case NumericalArrayTypeLongLong: [theFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; [theFormatter setMaximumFractionDigits:0]; [theFormatter setFormat:@"#;0;#"]; break; case NumericalArrayTypeFloat: case NumericalArrayTypeDouble: [theFormatter setNumberStyle:NSNumberFormatterScientificStyle]; [theFormatter setMaximumFractionDigits:3]; [theFormatter setFormat:@"#0.000;0.000;#0.000"]; break; } #else switch ( _arrayType ) { case NumericalArrayTypeInt: case NumericalArrayTypeLong: case NumericalArrayTypeLongLong: [theFormatter setFormat:@"#;#;#"]; break; case NumericalArrayTypeFloat: case NumericalArrayTypeDouble: [theFormatter setFormat:@"#0.000;0.000;#0.000"]; break; } #endif return theFormatter; } // - (NSNumber*) valueAtRow:(int)row column:(int)column { long arrayIndex = [self arrayIndexForRow:row andColumn:column]; switch ( _arrayType ) { case NumericalArrayTypeInt: return [NSNumber numberWithInt:((int*)_array)[arrayIndex]]; case NumericalArrayTypeLong: return [NSNumber numberWithLong:((long*)_array)[arrayIndex]]; case NumericalArrayTypeFloat: return [NSNumber numberWithFloat:((float*)_array)[arrayIndex]]; case NumericalArrayTypeDouble: return [NSNumber numberWithDouble:((double*)_array)[arrayIndex]]; } return nil; } // - (void) setValue:(id)value atRow:(int)row column:(int)column { int arrayIndex = [self arrayIndexForRow:row andColumn:column]; if ( [value isKindOfClass:[NSString class]] ) { NSScanner* aScanner = [[NSScanner alloc] initWithString:value]; switch ( _arrayType ) { case NumericalArrayTypeInt: [aScanner scanInt:((int*)_array) + arrayIndex]; break; case NumericalArrayTypeLong: { long long tempVal; [aScanner scanLongLong:&tempVal]; ((long*)_array)[arrayIndex] = (long)tempVal; break; } case NumericalArrayTypeLongLong: [aScanner scanLongLong:((long long*)_array) + arrayIndex]; break; case NumericalArrayTypeFloat: [aScanner scanFloat:((float*)_array) + arrayIndex]; break; case NumericalArrayTypeDouble: [aScanner scanDouble:((double*)_array) + arrayIndex]; break; } [aScanner release]; } else if ( [value isKindOfClass:[NSNumber class]] ) { switch ( _arrayType ) { case NumericalArrayTypeInt: ((int*)_array)[arrayIndex] = [value intValue]; break; case NumericalArrayTypeLong: ((long*)_array)[arrayIndex] = [value longValue]; break; case NumericalArrayTypeLongLong: ((long long*)_array)[arrayIndex] = [value longLongValue]; break; case NumericalArrayTypeFloat: ((float*)_array)[arrayIndex] = [value floatValue]; break; case NumericalArrayTypeDouble: ((double*)_array)[arrayIndex] = [value doubleValue]; break; } } } @end // #pragma mark - // @implementation NumericalTrianularArray - (id) initWithUpperTriangularArrayType:(NumericalArrayType)type array:(void*)array rows:(int)rows columns:(int)columns { return [self initWithUpperTriangularArrayType:type array:array rows:rows columns:columns orientation:NumericalArrayOrientationColumnMajor]; } // - (id) initWithUpperTriangularArrayType:(NumericalArrayType)type array:(void*)array rows:(int)rows columns:(int)columns orientation:(NumericalArrayOrientation)orientation { if ( (rows == columns) ) { if ( array == NULL ) { size_t bytes = (rows * rows + rows) / 2; switch ( type ) { case NumericalArrayTypeInt: bytes *= sizeof(int); break; case NumericalArrayTypeLong: bytes *= sizeof(long); break; case NumericalArrayTypeLongLong: bytes *= sizeof(long long); break; case NumericalArrayTypeFloat: bytes *= sizeof(float); break; case NumericalArrayTypeDouble: bytes *= sizeof(double); break; } array = calloc(1,bytes); } if ( self = [super initWithArrayType:type array:array rows:rows columns:columns orientation:orientation freeWhenDone:YES] ) { _triangularity = NumericalArrayTriangularUpper; } } else { [self release]; self = nil; } return self; } // - (id) initWithLowerTriangularArrayType:(NumericalArrayType)type array:(void*)array rows:(int)rows columns:(int)columns { return [self initWithLowerTriangularArrayType:type array:array rows:rows columns:columns orientation:NumericalArrayOrientationColumnMajor]; } // - (id) initWithLowerTriangularArrayType:(NumericalArrayType)type array:(void*)array rows:(int)rows columns:(int)columns orientation:(NumericalArrayOrientation)orientation { if ( self = [self initWithUpperTriangularArrayType:type array:array rows:rows columns:columns orientation:orientation] ) { _triangularity = NumericalArrayTriangularLower; } return self; } // - (NumericalArrayTriangular) triangularity { return _triangularity; } // - (long) arrayIndexForRow:(int)row andColumn:(int)column { if ( _triangularity == NumericalArrayTriangularUpper ) { if ( row > column ) { int tmp = row; row = column; column = tmp; } switch ( [self orientation] ) { case NumericalArrayOrientationColumnMajor: return (row + (column * (column + 1)) / 2); case NumericalArrayOrientationRowMajor: return (column + ((2 * [self columns] - row - 1) * row)/2); } } else { if ( row < column ) { int tmp = row; row = column; column = tmp; } switch ( [self orientation] ) { case NumericalArrayOrientationColumnMajor: return (row + ((2 * [self rows] - column - 1) * column)/2); case NumericalArrayOrientationRowMajor: return (column + (row * (row + 1)) / 2); } } // We should never get here, of course: return -1; } @end // #pragma mark - // @implementation NumericalArrayDS - (id) initWithNumericalArray:(id)array { if ( array && [array conformsToProtocol:@protocol(NumericalArrayAccess)] ) { _array = [array retain]; } else { [self release]; self = nil; } return self; } // - (void) dealloc { [_array release]; [super dealloc]; } // - (void) bindToTableView:(NSTableView*)aTable withDisplayAttributes:(NSDictionary*)displayAttributes { NSArray* extantColumns = [aTable tableColumns]; NSTableColumn* aColumn; NSCell* cellTemplate = [displayAttributes objectForKey:NumericalArrayDSCellTemplateKey]; int column = 0,columns = [_array columns]; float rowHeight = 0.0f; float initWidth = 25.0f; float minWidth = 50.0f; float maxWidth = 0.0f; NSNumber* aNumber; if ( cellTemplate == nil ) { NSNumberFormatter* theFormatter = [displayAttributes objectForKey:NumericalArrayDSFormatterKey]; NSFont* theFont = [displayAttributes objectForKey:NumericalArrayDSFontKey]; // Construct defaults: if ( theFormatter == nil ) theFormatter = [_array defaultFormatter]; if ( theFont == nil ) { theFont = [NSFont userFixedPitchFontOfSize:10.0f]; rowHeight = 14.0f; } // Default cell template: cellTemplate = [[[NSCell alloc] initTextCell:@""] autorelease]; [cellTemplate setFormatter:theFormatter]; [cellTemplate setEditable:YES]; [cellTemplate setFont:theFont]; [cellTemplate setAlignment:NSRightTextAlignment]; } else { rowHeight = [[cellTemplate font] pointSize] + 4.0f; } // Remove all columns from the table view: if ( extantColumns ) { NSEnumerator* eColumns = [extantColumns reverseObjectEnumerator]; while ( aColumn = [eColumns nextObject] ) { [aTable removeTableColumn:aColumn]; } } // Get column width stuff setup: if ( aNumber = [displayAttributes objectForKey:NumericalArrayDSInitialColumnWidthKey] ) { float newValue = [aNumber floatValue]; if ( newValue > 0.0f ) initWidth = newValue; } if ( aNumber = [displayAttributes objectForKey:NumericalArrayDSMinimumColumnWidthKey] ) { float newValue = [aNumber floatValue]; if ( newValue > initWidth ) minWidth = newValue; } if ( aNumber = [displayAttributes objectForKey:NumericalArrayDSMaximumColumnWidthKey] ) { float newValue = [aNumber floatValue]; if ( newValue > initWidth ) maxWidth = newValue; } // Create and add each column: while ( column < columns ) { aColumn = [[NSTableColumn alloc] initWithIdentifier:[NSNumber numberWithInt:column]]; [aColumn setWidth:initWidth]; [aColumn setMinWidth:minWidth]; if ( maxWidth > 0.0f ) [aColumn setMaxWidth:maxWidth]; [aColumn setDataCell:cellTemplate]; [[aColumn headerCell] setStringValue:[NSString stringWithFormat:@"%d",++column]]; [aTable addTableColumn:aColumn]; } // Final table setup: [aTable setRowHeight:rowHeight]; [aTable setHeaderView:nil]; #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_3 [aTable setGridStyleMask:NSTableViewSolidVerticalGridLineMask | NSTableViewSolidHorizontalGridLineMask]; #endif // Trigger a column resize, as well: [aTable sizeToFit]; [aTable setDataSource:self]; [aTable setDelegate:self]; [aTable reloadData]; } // - (NSString*) tableView:(NSTableView*)tv toolTipForCell:(NSCell*)cell rect:(NSRectPointer)rect tableColumn:(NSTableColumn*)tc row:(int)row mouseLocation:(NSPoint)mouseLocation { int col = [[tc identifier] intValue]; return [NSString stringWithFormat:@"(%d,%d)",row + 1,col + 1]; } // - (int) numberOfRowsInTableView:(NSTableView*)aTableView { return [_array rows]; } // - (id) tableView:(NSTableView*)aTableView objectValueForTableColumn:(NSTableColumn*)aTableColumn row:(int)rowIndex { return [_array valueAtRow:rowIndex column:[[aTableColumn identifier] intValue]]; } // - (void) tableView:(NSTableView*)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn*)aTableColumn row:(int)rowIndex { [_array setValue:anObject atRow:rowIndex column:[[aTableColumn identifier] intValue]]; } @end