// // OAConfigurableColumnOutlineView.m // OmniAppKit // // Created by Pierce T. Wetter III on 4/24/07. // Copyright 2007 __MyCompanyName__. All rights reserved. // #import "OAConfigurableColumnOutlineView.h" #import #import //#import //#import #import "assertions.h" @interface NSTableView (PrivateParts) - (void)_readPersistentTableColumns; - (void)_writePersistentTableColumns; @end @interface OAConfigurableColumnOutlineView (PrivateAPI) - (void)_OAcommonInit; - (void)_buildConfigurationMenu; - (void)_addItemWithTableColumn:(NSTableColumn *)column dataSource: (id) dataSource; - (NSMenuItem *)_itemForTableColumn: (NSTableColumn *) column; - (void)_toggleColumn:(id)sender; @end /*" Note that this class cannot have a 'deactivateTableColumns' ivar to store the inactive columns. The problem with that is that if NSTableView's column position/size saving code is turned on, it will blow away table columns that aren't listed in the default. This can lead to out-of-sync problems. Also note that this class doesn't subclass -addTableColumn: and -removeTableColumn to update the popup. "*/ @implementation OAConfigurableColumnOutlineView // // NSObject subclass // - initWithCoder:(NSCoder *)coder; { if (!(self = [super initWithCoder:coder])) return nil; [self _OAcommonInit]; return self; } - (void)dealloc; { [configurationMenu release]; [originalTableColumns release]; [super dealloc]; } // // NSView subclass // - initWithFrame:(NSRect)frame; { if (!(self = [super initWithFrame:frame])) return nil; [self _OAcommonInit]; return self; } - (NSMenu *)menuForEvent:(NSEvent *)event; { return configurationMenu; } // // NSTableView subclass // // We want this method to search both active and inactive columns (OOM depends upon this). Neither the configuration menu nor the tableColumns array is guaranteed to have all the items (the configuration menu will have all but those that cannot be configured and the tableColumns will have only the active columns). This is on place where our strategy of not adding an ivar for 'all table columsn' is wearing thin. - (NSTableColumn *)tableColumnWithIdentifier:(id)identifier; { NSArray *items; unsigned int itemIndex; NSMenuItem *item; NSTableColumn *column; // First check the configuration menu items = [configurationMenu itemArray]; itemIndex = [items count]; while (itemIndex--) { item = [items objectAtIndex: itemIndex]; column = [item representedObject]; if ([[column identifier] isEqual: identifier]) return column; } // Then check the table view (since it might have unconfigurable columns) items = [self tableColumns]; itemIndex = [items count]; while (itemIndex--) { column = [items objectAtIndex: itemIndex]; if ([[column identifier] isEqual: identifier]) return column; } return nil; } - (void) setDataSource: (id) dataSource; { [super setDataSource: dataSource]; confDataSourceFlags.menuString = [dataSource respondsToSelector: @selector(configurableColumnOutlineView:menuStringForColumn:)]; confDataSourceFlags.addSeparator = [dataSource respondsToSelector: @selector(configurableColumnOutlineView:shouldAddSeparatorAfterColumn:)]; confDataSourceFlags.allowToggle = [dataSource respondsToSelector: @selector(configurableColumnOutlineView:shouldAllowTogglingColumn:)]; confDataSourceFlags.willActivate = [dataSource respondsToSelector: @selector(configurableColumnOutlineView:willActivateColumn:)]; confDataSourceFlags.didActivate = [dataSource respondsToSelector: @selector(configurableColumnOutlineView:didActivateColumn:)]; confDataSourceFlags.willDeactivate = [dataSource respondsToSelector: @selector(configurableColumnOutlineView:willDeactivateColumn:)]; confDataSourceFlags.didDeactivate = [dataSource respondsToSelector: @selector(configurableColumnOutlineView:didDeactivateColumn:)]; // The new delegate may want to return different string [self _buildConfigurationMenu]; } // // New API // - (NSMenu *) configurationMenu; { return configurationMenu; } - (NSArray *)inactiveTableColumns; { NSMutableArray *inactiveTableColumns; NSArray *items; unsigned int itemIndex; NSMenuItem *item; NSTableColumn *column; inactiveTableColumns = [NSMutableArray array]; items = [configurationMenu itemArray]; itemIndex = [items count]; while (itemIndex--) { item = [items objectAtIndex: itemIndex]; column = [item representedObject]; if (![self isTableColumnActive: column]) [inactiveTableColumns addObject: column]; } return inactiveTableColumns; } - (void)activateTableColumn:(NSTableColumn *)column; { NSMenuItem *item; if ([[self tableColumns] indexOfObjectIdenticalTo:column] != NSNotFound) // Already active return; if (confDataSourceFlags.willActivate) [[self dataSource] configurableColumnOutlineView: self willActivateColumn: column]; item = [self _itemForTableColumn: column]; [item setState: YES]; [self addTableColumn:column]; // workaround for rdar://4508650. [NSTableView {add,remove}TableColumn:] honor autosaveTableColumns. if ([self autosaveTableColumns] && [self autosaveName] != nil) { if ([self respondsToSelector:@selector(_writePersistentTableColumns)]) [self _writePersistentTableColumns]; else OBASSERT_NOT_REACHED("no _writePersistentTableColumns on NSTableView"); } if (confDataSourceFlags.didActivate) [[self dataSource] configurableColumnOutlineView: self didActivateColumn: column]; } - (void)deactivateTableColumn:(NSTableColumn *)column; { NSMenuItem *item; if ([[self tableColumns] indexOfObjectIdenticalTo:column] == NSNotFound) // Already inactive return; if (confDataSourceFlags.willDeactivate) [[self dataSource] configurableColumnOutlineView: self willDeactivateColumn: column]; item = [self _itemForTableColumn: column]; [item setState: NO]; [self removeTableColumn:column]; // workaround for rdar://4508650. [NSTableView {add,remove}TableColumn:] honor autosaveTableColumns. if ([self autosaveTableColumns] && [self autosaveName] != nil) { if ([self respondsToSelector:@selector(_writePersistentTableColumns)]) [self _writePersistentTableColumns]; else OBASSERT_NOT_REACHED("no _writePersistentTableColumns on NSTableView"); } if (confDataSourceFlags.didDeactivate) [[self dataSource] configurableColumnOutlineView: self didDeactivateColumn: column]; } - (void)toggleTableColumn:(NSTableColumn *)column; { OBPRECONDITION(column); OBPRECONDITION([self _itemForTableColumn: column]); if ([self isTableColumnActive:column]) [self deactivateTableColumn:column]; else [self activateTableColumn:column]; [self tile]; if ([self columnAutoresizingStyle]> NSTableViewNoColumnAutoresizing) { [self sizeToFit]; } } - (BOOL)isTableColumnActive:(NSTableColumn *)column; { return [[self tableColumns] indexOfObject:column] != NSNotFound; } - (void)reloadData; { NSArray *menuItems; unsigned int itemIndex; [super reloadData]; menuItems = [configurationMenu itemArray]; for (itemIndex = 0; itemIndex < [menuItems count]; itemIndex++) { NSMenuItem *item; NSTableColumn *column; item = [menuItems objectAtIndex:itemIndex]; column = [item representedObject]; [[self _itemForTableColumn:column] setState:[self isTableColumnActive:column]]; } } @end @implementation OAConfigurableColumnOutlineView (PrivateAPI) - (void) _readPersistentTableColumns { if (!originalTableColumns) originalTableColumns=[[self tableColumns] copy]; [super _readPersistentTableColumns]; } - (void)_OAcommonInit; { if (!originalTableColumns) originalTableColumns=[[self tableColumns] copy]; [self _buildConfigurationMenu]; } - (void)_buildConfigurationMenu; { NSEnumerator *tableColumnEnum; NSTableColumn *tableColumn; id dataSource; [configurationMenu release]; configurationMenu = nil; dataSource = [self dataSource]; if (!dataSource) return; configurationMenu = [[NSMenu alloc] initWithTitle: @"Configure Columns"]; // Add items for all the columns. For columsn that aren't currently displayed, this will be where we store the pointer to the column. tableColumnEnum = [originalTableColumns objectEnumerator]; while ((tableColumn = [tableColumnEnum nextObject])) [self _addItemWithTableColumn:tableColumn dataSource: dataSource]; } - (void)_addItemWithTableColumn:(NSTableColumn *)column dataSource: (id) dataSource; { NSMenuItem *item; NSString *title = nil; // If we don't allow configuration, don't add the item to the menu if (confDataSourceFlags.allowToggle && ![dataSource configurableColumnOutlineView:self shouldAllowTogglingColumn:column]) return; if (confDataSourceFlags.menuString) title = [dataSource configurableColumnOutlineView:self menuStringForColumn:column]; if (!title) title = [[column headerCell] stringValue]; item = [[NSMenuItem alloc] initWithTitle:title action:@selector(_toggleColumn:) keyEquivalent: @""]; [item setState:[self isTableColumnActive:column]]; [item setRepresentedObject:column]; [configurationMenu addItem: item]; [item release]; if (confDataSourceFlags.addSeparator && [dataSource configurableColumnOutlineView:self shouldAddSeparatorAfterColumn:column]) [configurationMenu addItem: [NSMenuItem separatorItem]]; } - (NSMenuItem *)_itemForTableColumn: (NSTableColumn *) column; { NSArray *items; unsigned int itemIndex; NSMenuItem *item; items = [configurationMenu itemArray]; itemIndex = [items count]; while (itemIndex--) { item = [items objectAtIndex: itemIndex]; if (column == [item representedObject]) return item; } return nil; } - (void)_toggleColumn:(id)sender; { NSMenuItem *item; item = (NSMenuItem *)sender; OBASSERT([item isKindOfClass: [NSMenuItem class]]); [self toggleTableColumn: [item representedObject]]; } @end