// // DSTableViewCategories.m // Shared categories // // Created by David Sinclair on Mon Aug 12 2002. // Copyright © 2002 - 2007 Dejal Systems, LLC. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // // Redistributions of source code must retain this list of conditions and the following disclaimer. // // The name of Dejal Systems, LLC may not be used to endorse or promote products derived from this // software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE AUTHORS OR // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE // SOFTWARE OR THE USE OR OTHER DEALINGS IN THIS SOFTWARE. // #import "DSTableViewCategories.h" #import "DSAttributedStringCategories.h" #import "DSStringCategories.h" #import "DSWindowCategories.h" #import "DSDictionaryCategories.h" @interface NSObject (DSTableViewDelegate) - (NSMenu *)tableView:(NSTableView *)tableView menuForTableColumn:(NSTableColumn *)tableColumn row:(int)row; @end // ---------------------------------------------------------------------------------------- #pragma mark - // ---------------------------------------------------------------------------------------- @interface NSObject (DSTableViewCutCopyPasteDeleteDelegate) - (NSIndexSet *)tableView:(NSTableView *)tableView shouldCutRowIndexes:(NSIndexSet *)indexes; - (NSIndexSet *)tableView:(NSTableView *)tableView shouldCopyRowIndexes:(NSIndexSet *)indexes; - (int)tableView:(NSTableView *)tableView shouldPasteBeforeRow:(int)row; - (NSIndexSet *)tableView:(NSTableView *)tableView shouldDeleteRowIndexes:(NSIndexSet *)indexes; - (NSAttributedString *)tableView:(NSTableView *)tableView attributedStringValueForRow:(int)row; - (NSString *)tableView:(NSTableView *)tableView stringValueForRow:(int)row; - (NSString *)tableView:(NSTableView *)tableView delimiterForTableColumn:(NSTableColumn *)tableColumn row:(int)row; @end // ---------------------------------------------------------------------------------------- #pragma mark - // ---------------------------------------------------------------------------------------- @implementation NSTableView (DSTableViewCategories) /* selectNone: Deselects all lines in the table. */ - (IBAction)selectNone:(id)sender { [self deselectAll:sender]; } /* rowEnumerator Returns an enumerator of row numbers for all table rows. Written by DJS 2004-05. */ - (NSEnumerator *)rowEnumerator { NSMutableArray *array = [NSMutableArray arrayWithCapacity:[self numberOfRows]]; int row; for (row = 0; row < [self numberOfRows]; row++) [array addObject:[NSNumber numberWithInt:row]]; return [array objectEnumerator]; } /* selectedOrAllRowsEnumerator Returns an enumerator that either includes just the selected rows, if there are some selected, or all rows, if none are selected. Written by DJS 2002-08. */ - (NSEnumerator *)selectedOrAllRowsEnumerator { if ([self numberOfRows] == 0 || [self numberOfSelectedRows] > 0) return [self selectedRowEnumerator]; else return [self rowEnumerator]; } /* multipleSelectedOrAllRowsEnumerator Returns an enumerator that either includes just the selected rows, if there are more than one selected, or all rows if none or one are selected. Written by DJS 2004-05. */ - (NSEnumerator *)multipleSelectedOrAllRowsEnumerator { if ([self numberOfRows] == 0 || [self numberOfSelectedRows] > 1) return [self selectedRowEnumerator]; else return [self rowEnumerator]; } /* selectFirstRowExtendingSelection: Selects the first row, either as well as any existing selection, or instead. Written by DJS 2007-04. */ - (void)selectFirstRowExtendingSelection:(BOOL)extend; { if ([self numberOfRows] > 0) [self selectRowIndexes:[NSIndexSet indexSetWithIndex:0] byExtendingSelection:extend]; } /* selectLastRowExtendingSelection: Selects the last row, either as well as any existing selection, or instead. Written by DJS 2007-04. */ - (void)selectLastRowExtendingSelection:(BOOL)extend; { if ([self numberOfRows] > 0) [self selectRowIndexes:[NSIndexSet indexSetWithIndex:[self numberOfRows] - 1] byExtendingSelection:extend]; } /* editColumnWithIdentifier:row: Edits the cell with the column identifier and row, selecting the row first if necessary. A slightly more convenient version of -editColumn:row:withEvent:select:. Does nothing if the row is out of range or the column couldn't be found. Written by DJS 2007-04. */ - (void)editColumnWithIdentifier:(NSString *)columnIdentfier row:(int)row; { int column = [self columnWithIdentifier:columnIdentfier]; if (row < 0 || row >= [self numberOfRows] || column < 0) return; [self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; [self editColumn:column row:row withEvent:nil select:YES]; } /* tableColumnEnumerator Returns an enumerator of all of the columns in the table. Written by DJS 2004-02. */ - (NSEnumerator *)tableColumnEnumerator { return [[self tableColumns] objectEnumerator]; } /* tableColumnAtIndex: Returns the indexth table column. Written by DJS 2006-10. */ - (NSTableColumn *)tableColumnAtIndex:(int)index; { return [[self tableColumns] objectAtIndex:index]; } /* indexOfFirstEditableTableColumn Returns the index of the first editable column in the table, or NSNotFound if there are none. Written by DJS 2006-10. */ - (int)indexOfFirstEditableTableColumn; { int index = 0; int count = [self numberOfColumns]; while (index < count && ![[self tableColumnAtIndex:index] isEditable]) index++; return index < count ? index : NSNotFound; } /* firstEditableTableColumn Returns the first editable column in the table, or nil if there are none. Written by DJS 2006-10. */ - (NSTableColumn *)firstEditableTableColumn; { int index = [self indexOfFirstEditableTableColumn]; return index != NSNotFound ? [self tableColumnAtIndex:index] : nil; } /* addTableColumnWithIdentifier:title:editable:sizable:width: Creates a new text column with the specified attributes, and adds it to the receiver. The new column is returned, in case you want to change any other attributes. Written by DJS 2004-02. */ - (NSTableColumn *)addTableColumnWithIdentifier:(NSString *)identifier title:(NSString *)title editable:(BOOL)editable resizable:(BOOL)resizable width:(float)width { return [self addTableColumnWithIdentifier:identifier title:title editable:editable resizable:resizable sortable:NO ascending:YES width:width alignment:NSNaturalTextAlignment]; } /* addTableColumnWithIdentifier:title:editable:resizable:sortable:ascending:width:alignment: Creates a new text column with the specified attributes, and adds it to the receiver. The new column is returned, in case you want to change any other attributes. Written by DJS 2004-05. Changed by DJS 2006-06 to use -setResizingMask: if available (10.4 or later). */ - (NSTableColumn *)addTableColumnWithIdentifier:(NSString *)identifier title:(NSString *)title editable:(BOOL)editable resizable:(BOOL)resizable sortable:(BOOL)sortable ascending:(BOOL)ascending width:(float)width alignment:(NSTextAlignment)alignment { NSTableColumn *tableColumn = [[[NSTableColumn alloc] initWithIdentifier:identifier] autorelease]; [[tableColumn headerCell] setStringValue:title]; [[tableColumn headerCell] setAlignment:alignment]; [[tableColumn dataCell] setFont:[[tableColumn headerCell] font]]; [[tableColumn dataCell] setAlignment:alignment]; [tableColumn setEditable:editable]; if ([tableColumn respondsToSelector:@selector(setResizingMask:)]) [tableColumn setResizingMask:resizable ? NSTableColumnUserResizingMask : NSTableColumnNoResizing]; else [tableColumn setResizable:resizable]; [tableColumn setMinWidth:width < 50.0 ? width : 50.0]; [tableColumn setMaxWidth:3000.0]; [tableColumn setWidth:width]; if (sortable && [tableColumn respondsToSelector:@selector(setSortDescriptorPrototype:)]) { NSSortDescriptor *descriptor = [[[NSSortDescriptor alloc] initWithKey:identifier ascending:ascending] autorelease]; [tableColumn setSortDescriptorPrototype:descriptor]; } [self addTableColumn:tableColumn]; return tableColumn; } /* addTableColumnWithColumnInfo: Creates a new text column with the attributes specified in a dictionary with "Identifier", "Name", "Width", and "Editable" keys. If the "Ascending" key is present, the column is sortable. Written by DJS 2004-05. */ - (NSTableColumn *)addTableColumnWithColumnInfo:(NSDictionary *)columnInfo { NSString *identifier = [columnInfo objectForKey:@"Identifier"]; NSString *title = [columnInfo objectForKey:@"Name"]; NSNumber *ascendingNum = [columnInfo objectForKey:@"Ascending"]; float width = [[columnInfo objectForKey:@"Width"] floatValue]; NSTextAlignment alignment = [[columnInfo objectForKey:@"Alignment"] intValue]; BOOL isEditable = [[columnInfo objectForKey:@"Editable"] boolValue]; BOOL isResizable = width != 16.0; BOOL isSortable = ascendingNum != nil; BOOL isAscending = [ascendingNum boolValue]; if (width < 12.0) width = 100.0; return [self addTableColumnWithIdentifier:identifier title:title editable:isEditable resizable:isResizable sortable:isSortable ascending:isAscending width:width alignment:alignment]; } /* addTableColumnsWithArrayOfColumnInfo:removeFirst: Creates new text columns as described in an array of dictionaries as for -addTableColumnsWithArrayOfColumnInfo:, above. If removeAll is YES, any existing columns are first removed. If sizeLast is YES, the last column is resized to fit. Written by DJS 2004-05. */ - (void)addTableColumnsWithArrayOfColumnInfo:(NSArray *)columns removeAll:(BOOL)removeAll sizeLast:(BOOL)sizeLast { if (removeAll) [self removeTableColumns:[self tableColumns]]; NSEnumerator *enumerator = [columns objectEnumerator]; NSDictionary *columnInfo = nil; while ((columnInfo = [enumerator nextObject])) { [self addTableColumnWithColumnInfo:columnInfo]; } if (sizeLast) [self sizeLastColumnToFit]; } /* insertTableColumn:atIndex: Similar to -addTableColumn:, this adds the specified table column at the indicated index. The column is retained by the receiver. Written by DJS 2005-10. */ - (void)insertTableColumn:(NSTableColumn *)aColumn atIndex:(int)index; { [self addTableColumn:aColumn]; int lastIndex = [self numberOfColumns] - 1; // Only bother to move if not already in the right place (new columns are added at the end): if (index >= 0 && index < lastIndex) [self moveColumn:lastIndex toColumn:index]; } /* removeTableColumns: Given an array of NSTableColumns (as returned by -tableColumns), removes them from the receiver. Written by DJS 2004-02. */ - (void)removeTableColumns:(NSArray *)columns { // Since this would alter the columns, make a copy of the array in case it is really the actual array of columns: NSEnumerator *enumerator = [[[columns copy] autorelease] objectEnumerator]; NSTableColumn *tableColumn; while ((tableColumn = [enumerator nextObject])) { [self removeTableColumn:tableColumn]; } } /* registerDefaultSortDescriptorForTableColumnWithIdentifier:ascending: If the table doesn't already have sort descriptors, this will set the column with the specified identifier to be the currently sorted one, in ascending or descending order. If there is no table column with that identifier (or it is nil), the first table column is used, if there is one. Written by DJS 2004-05. */ - (void)registerDefaultSortDescriptorForTableColumnWithIdentifier:(NSString *)identifier ascending:(BOOL)ascending { NSTableColumn *tableColumn = [self tableColumnWithIdentifier:identifier]; [self registerDefaultSortDescriptorForTableColumn:tableColumn ascending:ascending]; } /* registerDefaultSortDescriptorForTableColumn:ascending: If the table doesn't already have sort descriptors, this will set the specified column to be the currently sorted one, in ascending or descending order. If the table column is nil, the first table column is used, if there is one. Written by DJS 2004-05. */ - (void)registerDefaultSortDescriptorForTableColumn:(NSTableColumn *)tableColumn ascending:(BOOL)ascending { if ([self respondsToSelector:@selector(sortDescriptors)] && ![[self sortDescriptors] count]) { if (!tableColumn) { NSArray *columns = [self tableColumns]; if ([columns count]) tableColumn = [columns objectAtIndex:0]; } if (tableColumn) { NSSortDescriptor *descriptor = [[[NSSortDescriptor alloc] initWithKey:[tableColumn identifier] ascending:ascending] autorelease]; [self setSortDescriptors:[NSArray arrayWithObject:descriptor]]; } } } - (void)setHeaderImageNamed:(NSString *)imageName inTableColumnWithIdentifier:(NSString *)identifier { NSTableColumn *column = [self tableColumnWithIdentifier:identifier]; NSFileWrapper *fileWrapper = [[[NSFileWrapper alloc] initWithPath:[[NSBundle mainBundle] pathForResource:imageName ofType:@"tiff"]] autorelease]; NSTextAttachment *attachment = [[[NSTextAttachment alloc] initWithFileWrapper:fileWrapper] autorelease]; NSAttributedString *string = [NSAttributedString attributedStringWithAttachment:attachment]; [[column headerCell] setAttributedStringValue:string]; } - (void)setDataImageNamed:(NSString *)imageName inTableColumnWithIdentifier:(NSString *)identifier { NSTableColumn *column = [self tableColumnWithIdentifier:identifier]; NSImage *image = [NSImage imageNamed:imageName]; NSImageCell *cell = [[[NSImageCell alloc] initImageCell:image] autorelease]; [column setDataCell:cell]; } - (void)setDataProgressCellClass:(Class)class withAltImageNamed:(NSString *)imageName inTableColumnWithIdentifier:(NSString *)identifier { NSTableColumn *column = [self tableColumnWithIdentifier:identifier]; NSImage *image = [NSImage imageNamed:imageName]; NSCell *cell; if ([class instancesRespondToSelector:@selector(initProgressCellWithAltImage:)]) cell = [[[class alloc] performSelector:@selector(initProgressCellWithAltImage:) withObject:image] autorelease]; else cell = [[[NSImageCell alloc] initImageCell:image] autorelease]; [column setDataCell:cell]; } - (void)setHeaderAndDataImageNamed:(NSString *)imageName inTableColumnWithIdentifier:(NSString *)identifier { [self setHeaderImageNamed:imageName inTableColumnWithIdentifier:identifier]; [self setDataImageNamed:imageName inTableColumnWithIdentifier:identifier]; } - (void)setHeaderAndDataProgressCellClass:(Class)class withAltImageNamed:(NSString *)imageName inTableColumnWithIdentifier:(NSString *)identifier { [self setHeaderImageNamed:imageName inTableColumnWithIdentifier:identifier]; [self setDataProgressCellClass:class withAltImageNamed:imageName inTableColumnWithIdentifier:identifier]; } /* truncatedString:withIndicator:forTableColumn: Returns the specified string either intact, or truncated with the indicator string added if the original string is too long to fit in the table column. The indicator string would typically be an ellipsis (...). This method is ideal to call in your -tableView:objectValueForTableColumn:row: data source method. Written by DJS 2004-01. */ - (NSString *)truncatedString:(NSString *)string withIndicator:(NSString *)indicator forTableColumn:(NSTableColumn *)tableColumn { NSCell *cell = [tableColumn dataCell]; int desiredWidth = [cell titleRectForBounds:NSMakeRect(0, 0, [tableColumn width] - 50, [self rowHeight])].size.width; return [string truncatedStringWithIndicator:indicator forFont:[cell font] withWidth:desiredWidth]; } /* menuForEvent: Override of a NSView method, to allow a contextual menu for an individual row and column. You should implement -tableView:menuForTableColumn:row: in your table delegate. If that isn't implemented, this returns the default menu. Written by DJS 2004-06. */ - (NSMenu *)menuForEvent:(NSEvent *)event { NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil]; int column = [self columnAtPoint:point]; int row = [self rowAtPoint:point]; if (column >= 0 && row >= 0 && [[self delegate] respondsToSelector:@selector(tableView:menuForTableColumn:row:)]) return [[self delegate] tableView:self menuForTableColumn:[[self tableColumns] objectAtIndex:column] row:row]; else return [super menuForEvent:event]; } /* addKey:withValue:toDictionary: If the data source for the receiver is a dictionary, displayed via "Key" and "Value" columns, this method will add a new key to that dictionary, usually called from the "+" button's action method. Digits are appended to the key to make it unique, if necessary. Note that this could and should be done instead with bindings. Remove this method once existing uses have been upgraded. Written by DJS 2006-10. */ - (void)addKey:(NSString *)key withValue:(NSString *)value toDictionary:(NSMutableDictionary *)dict; { NSString *composedKey = key; int extra = 2; if (![key length] || [dict objectForKey:key]) while ((composedKey = [NSString stringWithFormat:@"%@%d", key, extra]) && [dict objectForKey:composedKey]) extra++; [dict setObject:value forKey:composedKey]; [self reloadData]; int row = [[dict sortedKeys] indexOfObject:composedKey]; int column = [self indexOfFirstEditableTableColumn]; [self selectRow:row byExtendingSelection:NO]; if (column >= 0) [self editColumn:column row:row withEvent:nil select:YES]; } /* removeSelectedKeyFromDictionary: If the data source for the receiver is a dictionary, displayed via "Key" and "Value" columns, this method will remove the key of the selected row from that dictionary, usually called from the "-" button's action method. Only supports deleting a single row, so multiple selection shouldn't be enabled. Note that this could and should be done instead with bindings. Remove this method once existing uses have been upgraded. Written by DJS 2006-10. */ - (void)removeSelectedKeyFromDictionary:(NSMutableDictionary *)dict; { [[self window] forceEndEditingForView:self]; int row = [self selectedRow]; NSString *key = [[dict sortedKeys] objectAtIndex:row]; if (!dict) return; [dict removeObjectForKey:key]; [self reloadData]; } @end // ---------------------------------------------------------------------------------------- #pragma mark - // ---------------------------------------------------------------------------------------- @implementation NSTableView (DSTableViewCutCopyPasteDeleteDelegate) - (BOOL)_doCopyIndexes:(NSIndexSet *)indexes { NSMutableAttributedString *output = [NSMutableAttributedString attributedString]; BOOL canProvideAttributedString = [[self delegate] respondsToSelector:@selector(tableView:attributedStringValueForRow:)]; BOOL canProvidePlainString = [[self delegate] respondsToSelector:@selector(tableView:stringValueForRow:)]; BOOL canProvideSomeValue = canProvideAttributedString || canProvidePlainString; // NSIndexSet is a rather ugly collection class. I should implement enumerator extensions for it sometime. // Scan through the row indexes: int row = [indexes firstIndex]; while (row != NSNotFound) { if (canProvideSomeValue) { // The delegate wants to provide the text for the row, so get it: NSAttributedString *attribString = nil; if (canProvideAttributedString) attribString = [[self delegate] tableView:self attributedStringValueForRow:row]; else if (canProvidePlainString) attribString = [NSAttributedString attributedStringWithString:[[self delegate] tableView:self stringValueForRow:row]]; [output appendAttributedString:attribString]; [output appendAttributedString:[NSAttributedString attributedStringWithString:@"\n"]]; } else { // The delegate doesn't want to override the row text, so built it automatically: NSAttributedString *columnAttribString = nil; id value = nil; NSEnumerator *enumerator = [self tableColumnEnumerator]; NSTableColumn *tableColumn = nil; NSTableColumn *lastColumn = [[self tableColumns] lastObject]; BOOL canProvideDelimiter = [[self delegate] respondsToSelector:@selector(tableView:delimiterForTableColumn:row:)]; NSAttributedString *delimiter = nil; // Scan through the columns and get an attributed string for each: while ((tableColumn = [enumerator nextObject])) { // Ask the standard table data source method for the column value: value = [[self delegate] tableView:self objectValueForTableColumn:tableColumn row:row]; // Handle each expected value kind appropriately: if ([value isKindOfClass:[NSImage class]]) { // It's an image; convert to a TIFF and write it out to a temporary location: NSData *tiff = [value TIFFRepresentation]; NSString *path = [[NSTemporaryDirectory() stringByAppendingPathComponent:[[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleName"]] validatedDirectoryPath]; path = [[path stringByAppendingPathComponent:[value name]] stringByAppendingPathExtension:@".tiff"]; [tiff writeToFile:path atomically:NO]; // Create a file wrapper for the image file: NSFileWrapper *wrapper = [[[NSFileWrapper alloc] initRegularFileWithContents:tiff] autorelease]; [wrapper setPreferredFilename:path]; [wrapper setIcon:value]; // Create a text attachment with the wrapper: NSTextAttachment *textAtt = [[[NSTextAttachment alloc] initWithFileWrapper:wrapper] autorelease]; // Create an attributed string with the attachment: columnAttribString = [NSAttributedString attributedStringWithAttachment:textAtt]; } else if ([value isKindOfClass:[NSNumber class]]) { // It's a number; use the attached formatter to get the pretty format; if no formatter, it is included as-is: NSFormatter *formatter = [[tableColumn dataCell] formatter]; NSString *formatted = [formatter stringForObjectValue:value]; if (formatted) columnAttribString = [NSAttributedString attributedStringWithString:formatted]; else columnAttribString = [NSAttributedString attributedStringWithString:[value description]]; } else if ([value isKindOfClass:[NSString class]]) { // It's a string; just get it as an attributed string: columnAttribString = [NSAttributedString attributedStringWithString:value]; } else columnAttribString = nil; // Append the interpreted column value to the output: if (columnAttribString) [output appendAttributedString:columnAttribString]; // Add a linefeed at the end of line: if (tableColumn == lastColumn) [output appendAttributedString:[NSAttributedString attributedStringWithString:@"\n"]]; else { // If the delegate implements -tableView:delimiterForTableColumn:row:, ask it for the delimiter; otherwise, or if that returns nil, default to a tab: if (!canProvideDelimiter || !(delimiter = [NSAttributedString attributedStringWithString:[[self delegate] tableView:self delimiterForTableColumn:tableColumn row:row]])) delimiter = [NSAttributedString attributedStringWithString:@"\t"]; [output appendAttributedString:delimiter]; } } } // Step to the next row: row = [indexes indexGreaterThanIndex:row]; } // Add tab stops: NSMutableParagraphStyle *paragraph = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] autorelease]; NSMutableArray *tabStops = [NSMutableArray arrayWithCapacity:[self numberOfColumns]]; NSEnumerator *enumerator = [self tableColumnEnumerator]; NSTableColumn *tableColumn = nil; float position = 0.0; // Add tab stops based on the column positions: while ((tableColumn = [enumerator nextObject])) { position += [tableColumn width]; NSTextTab *tabStop = [[NSTextTab alloc] initWithType:NSLeftTabStopType location:position]; [tabStops addObject:tabStop]; } [paragraph setTabStops:tabStops]; NSDictionary *attributes = [NSDictionary dictionaryWithObject:paragraph forKey:NSParagraphStyleAttributeName]; [output addAttributes:attributes range:[output allRange]]; // Convert the constructed output attributed string to RTFD and a plain string: NSPasteboard *pboard = [NSPasteboard generalPasteboard]; NSData *outputRTFD = [output RTFDValue]; NSString *outputString = [output string]; // Remove the attachment characters from the plain text edition. A nicer idea might be to use the image or column name as the attachment filename, and replace with that (by calling -attributesAtIndex:effectiveRange:), though the image names aren't really human-readable, so maybe not: outputString = [outputString stringByReplacingAllOccurrencesOf:[NSString stringWithFormat:@"%C", NSAttachmentCharacter] with:@""]; if (outputRTFD && outputString) { // Declare types: [pboard declareTypes:[NSArray arrayWithObjects:NSRTFDPboardType, NSStringPboardType, nil] owner:self]; // Copy values to pasteboard: [pboard setData:outputRTFD forType:NSRTFDPboardType]; [pboard setString:outputString forType:NSStringPboardType]; return YES; } else return NO; } - (BOOL)_doDeleteIndexes:(NSIndexSet *)indexes { // ¦¦ to be implemented return NO; } - (IBAction)cut:(id)sender { NSIndexSet *indexes = [self shouldCutRowIndexes]; if (!indexes) return; // This method is actually fully implemented; but _doDeleteIndexes: is not: NSLog(@"The -cut: method for table views is not implemented yet!"); // ¦¦ log // Only Delete if the Copy was successful: if ([self _doCopyIndexes:indexes]) [self _doDeleteIndexes:indexes]; } - (IBAction)copy:(id)sender { NSIndexSet *indexes = [self shouldCopyRowIndexes]; if (!indexes) return; [self _doCopyIndexes:indexes]; } - (IBAction)paste:(id)sender { int row = [self shouldPasteBeforeRow]; if (row < 0) return; NSLog(@"The -paste: method for table views is not implemented yet!"); // ¦¦ log /* NSImage *image = [[[NSImage alloc] initWithPasteboard:[NSPasteboard generalPasteboard]] autorelease]; if (image) { [self setImage:image]; [self sendAction:[self action] to:[self target]]; } */ } - (IBAction)delete:(id)sender { NSIndexSet *indexes = [self shouldDeleteRowIndexes]; if (!indexes) return; // This method is actually fully implemented; but _doDeleteIndexes: is not: NSLog(@"The -delete: method for table views is not implemented yet!"); // ¦¦ log [self _doDeleteIndexes:indexes]; [self sendAction:[self action] to:[self target]]; } - (BOOL)validateMenuItem:(NSMenuItem *)item { SEL action = [item action]; if (action == @selector(cut:)) return ([self shouldCutRowIndexes] != nil); else if (action == @selector(copy:)) return ([self shouldCopyRowIndexes] != nil); else if (action == @selector(paste:)) return ([self shouldPasteBeforeRow] >= 0); else if (action == @selector(delete:)) return ([self shouldDeleteRowIndexes] != nil); else return YES; } /* shouldCutRowIndexes If the delegate responds to -tableView:shouldCutRowIndexes:, it is invoked with the selected row indexes as a parameter. The delegate should either return it intact if those rows are cuttable, or modify it (by making a mutable copy) to remove some rows, or return nil if Cut should not be allowed. If there is no selection, nil is returned without asking the delegate. You shouldn't need to call this method. Written by DJS 2004-12. */ - (NSIndexSet *)shouldCutRowIndexes { NSIndexSet *indexes = [self selectedRowIndexes]; if ([indexes count] && [[self delegate] respondsToSelector:@selector(tableView:shouldCutRowIndexes:)]) return [[self delegate] tableView:self shouldCutRowIndexes:indexes]; else return nil; } /* shouldCopyRowIndexes If the delegate responds to -tableView:shouldCopyRowIndexes:, it is invoked with the selected row indexes as a parameter. The delegate should either return it intact if those rows are copyable, or modify it (by making a mutable copy) to remove some rows, or return nil if Copy should not be allowed. If there is no selection, nil is returned without asking the delegate. Written by DJS 2004-12. */ - (NSIndexSet *)shouldCopyRowIndexes { NSIndexSet *indexes = [self selectedRowIndexes]; if ([indexes count] && [[self delegate] respondsToSelector:@selector(tableView:shouldCopyRowIndexes:)]) return [[self delegate] tableView:self shouldCopyRowIndexes:indexes]; else return nil; } /* shouldPasteBeforeRow If the delegate responds to -tableView:shouldPasteBeforeRow:, it is invoked with the first selected row as a parameter (or 0 if there is no selection, so it will paste before the first row). The delegate should either return it intact if pasting before that row is acceptable, or return a different row, or return -1 if Paste should not be allowed. Written by DJS 2004-12. */ - (int)shouldPasteBeforeRow { int row = [self selectedRow]; if (row < 0) row = 0; if ([[self delegate] respondsToSelector:@selector(tableView:shouldPasteBeforeRow:)]) return [[self delegate] tableView:self shouldPasteBeforeRow:row]; else return -1; } /* shouldDeleteRowIndexes If the delegate responds to -tableView:shouldDeleteRowIndexes:, it is invoked with the selected row indexes as a parameter. The delegate should either return it intact if those rows are deletable, or modify it (by making a mutable copy) to remove some rows, or return nil if Delete should not be allowed. If there is no selection, nil is returned without asking the delegate. Written by DJS 2004-12. */ - (NSIndexSet *)shouldDeleteRowIndexes { NSIndexSet *indexes = [self selectedRowIndexes]; if ([indexes count] && [[self delegate] respondsToSelector:@selector(tableView:shouldDeleteRowIndexes:)]) return [[self delegate] tableView:self shouldDeleteRowIndexes:indexes]; else return nil; } @end