![]() |
API 0.9.5
|
00001 /* 00002 * CPOutlineView.j 00003 * AppKit 00004 * 00005 * Created by Francisco Tolmasky. 00006 * Copyright 2009, 280 North, Inc. 00007 * 00008 * This library is free software; you can redistribute it and/or 00009 * modify it under the terms of the GNU Lesser General Public 00010 * License as published by the Free Software Foundation; either 00011 * version 2.1 of the License, or (at your option) any later version. 00012 * 00013 * This library is distributed in the hope that it will be useful, 00014 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00016 * Lesser General Public License for more details. 00017 * 00018 * You should have received a copy of the GNU Lesser General Public 00019 * License along with this library; if not, write to the Free Software 00020 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00021 */ 00022 00023 00024 00025 CPOutlineViewColumnDidMoveNotification = @"CPOutlineViewColumnDidMoveNotification"; 00026 CPOutlineViewColumnDidResizeNotification = @"CPOutlineViewColumnDidResizeNotification"; 00027 CPOutlineViewItemDidCollapseNotification = @"CPOutlineViewItemDidCollapseNotification"; 00028 CPOutlineViewItemDidExpandNotification = @"CPOutlineViewItemDidExpandNotification"; 00029 CPOutlineViewItemWillCollapseNotification = @"CPOutlineViewItemWillCollapseNotification"; 00030 CPOutlineViewItemWillExpandNotification = @"CPOutlineViewItemWillExpandNotification"; 00031 CPOutlineViewSelectionDidChangeNotification = @"CPOutlineViewSelectionDidChangeNotification"; 00032 CPOutlineViewSelectionIsChangingNotification = @"CPOutlineViewSelectionIsChangingNotification"; 00033 00034 var CPOutlineViewDataSource_outlineView_setObjectValue_forTableColumn_byItem_ = 1 << 1, 00035 CPOutlineViewDataSource_outlineView_shouldDeferDisplayingChildrenOfItem_ = 1 << 2, 00036 00037 CPOutlineViewDataSource_outlineView_acceptDrop_item_childIndex_ = 1 << 3, 00038 CPOutlineViewDataSource_outlineView_validateDrop_proposedItem_proposedChildIndex_ = 1 << 4, 00039 CPOutlineViewDataSource_outlineView_validateDrop_proposedRow_proposedDropOperation_ = 1 << 5, 00040 CPOutlineViewDataSource_outlineView_namesOfPromisedFilesDroppedAtDestination_forDraggedItems_ = 1 << 6, 00041 00042 CPOutlineViewDataSource_outlineView_itemForPersistentObject_ = 1 << 7, 00043 CPOutlineViewDataSource_outlineView_persistentObjectForItem_ = 1 << 8, 00044 00045 CPOutlineViewDataSource_outlineView_writeItems_toPasteboard_ = 1 << 9, 00046 00047 CPOutlineViewDataSource_outlineView_sortDescriptorsDidChange_ = 1 << 10; 00048 00049 var CPOutlineViewDelegate_outlineView_dataViewForTableColumn_item_ = 1 << 1, 00050 CPOutlineViewDelegate_outlineView_didClickTableColumn_ = 1 << 2, 00051 CPOutlineViewDelegate_outlineView_didDragTableColumn_ = 1 << 3, 00052 CPOutlineViewDelegate_outlineView_heightOfRowByItem_ = 1 << 4, 00053 CPOutlineViewDelegate_outlineView_isGroupItem_ = 1 << 5, 00054 CPOutlineViewDelegate_outlineView_mouseDownInHeaderOfTableColumn_ = 1 << 6, 00055 CPOutlineViewDelegate_outlineView_nextTypeSelectMatchFromItem_toItem_forString_ = 1 << 7, 00056 CPOutlineViewDelegate_outlineView_selectionIndexesForProposedSelection_ = 1 << 8, 00057 CPOutlineViewDelegate_outlineView_shouldCollapseItem_ = 1 << 9, 00058 CPOutlineViewDelegate_outlineView_shouldEditTableColumn_item_ = 1 << 10, 00059 CPOutlineViewDelegate_outlineView_shouldExpandItem_ = 1 << 11, 00060 CPOutlineViewDelegate_outlineView_shouldReorderColumn_toColumn_ = 1 << 12, 00061 CPOutlineViewDelegate_outlineView_shouldSelectItem_ = 1 << 13, 00062 CPOutlineViewDelegate_outlineView_shouldSelectTableColumn_ = 1 << 14, 00063 CPOutlineViewDelegate_outlineView_shouldShowOutlineViewForItem_ = 1 << 15, 00064 CPOutlineViewDelegate_outlineView_shouldShowViewExpansionForTableColumn_item_ = 1 << 16, 00065 CPOutlineViewDelegate_outlineView_shouldTrackView_forTableColumn_item_ = 1 << 17, 00066 CPOutlineViewDelegate_outlineView_shouldTypeSelectForEvent_withCurrentSearchString_ = 1 << 18, 00067 CPOutlineViewDelegate_outlineView_sizeToFitWidthOfColumn_ = 1 << 19, 00068 CPOutlineViewDelegate_outlineView_toolTipForView_rect_tableColumn_item_mouseLocation_ = 1 << 20, 00069 CPOutlineViewDelegate_outlineView_typeSelectStringForTableColumn_item_ = 1 << 21, 00070 CPOutlineViewDelegate_outlineView_willDisplayOutlineView_forTableColumn_item_ = 1 << 22, 00071 CPOutlineViewDelegate_outlineView_willDisplayView_forTableColumn_item_ = 1 << 23, 00072 CPOutlineViewDelegate_selectionShouldChangeInOutlineView_ = 1 << 24, 00073 CPOutlineViewDelegate_outlineView_menuForTableColumn_item_ = 1 << 25; 00074 00075 CPOutlineViewDropOnItemIndex = -1; 00076 00077 var CPOutlineViewCoalesceSelectionNotificationStateOff = 0, 00078 CPOutlineViewCoalesceSelectionNotificationStateOn = 1, 00079 CPOutlineViewCoalesceSelectionNotificationStateDid = 2; 00080 00096 @implementation CPOutlineView : CPTableView 00097 { 00098 id _outlineViewDataSource; 00099 id _outlineViewDelegate; 00100 CPTableColumn _outlineTableColumn; 00101 00102 float _indentationPerLevel; 00103 BOOL _indentationMarkerFollowsDataView; 00104 00105 CPInteger _implementedOutlineViewDataSourceMethods; 00106 CPInteger _implementedOutlineViewDelegateMethods; 00107 00108 Object _rootItemInfo; 00109 CPMutableArray _itemsForRows; 00110 Object _itemInfosForItems; 00111 00112 CPControl _disclosureControlPrototype; 00113 CPArray _disclosureControlsForRows; 00114 CPData _disclosureControlData; 00115 CPArray _disclosureControlQueue; 00116 00117 BOOL _shouldRetargetItem; 00118 id _retargetedItem; 00119 00120 BOOL _shouldRetargetChildIndex; 00121 CPInteger _retargedChildIndex; 00122 CPTimer _dragHoverTimer; 00123 id _dropItem; 00124 00125 BOOL _coalesceSelectionNotificationState; 00126 } 00127 00128 - (id)initWithFrame:(CGRect)aFrame 00129 { 00130 self = [super initWithFrame:aFrame]; 00131 00132 if (self) 00133 { 00134 _selectionHighlightStyle = CPTableViewSelectionHighlightStyleSourceList; 00135 00136 // The root item has weight "0", thus represents the weight solely of its descendants. 00137 _rootItemInfo = { isExpanded:YES, isExpandable:NO, level:-1, row:-1, children:[], weight:0 }; 00138 00139 _itemsForRows = []; 00140 _itemInfosForItems = { }; 00141 _disclosureControlsForRows = []; 00142 00143 _retargetedItem = nil; 00144 _shouldRetargetItem = NO; 00145 00146 _retargedChildIndex = nil; 00147 _shouldRetargetChildIndex = NO; 00148 _startHoverTime = nil; 00149 00150 [self setIndentationPerLevel:16.0]; 00151 [self setIndentationMarkerFollowsDataView:YES]; 00152 00153 [super setDataSource:[[_CPOutlineViewTableViewDataSource alloc] initWithOutlineView:self]]; 00154 [super setDelegate:[[_CPOutlineViewTableViewDelegate alloc] initWithOutlineView:self]]; 00155 00156 [self setDisclosureControlPrototype:[[CPDisclosureButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 10.0)]]; 00157 } 00158 00159 return self; 00160 } 00212 - (void)setDataSource:(id)aDataSource 00213 { 00214 if (_outlineViewDataSource === aDataSource) 00215 return; 00216 00217 if (![aDataSource respondsToSelector:@selector(outlineView:child:ofItem:)]) 00218 [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:child:ofItem:'"]; 00219 00220 if (![aDataSource respondsToSelector:@selector(outlineView:isItemExpandable:)]) 00221 [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:isItemExpandable:'"]; 00222 00223 if (![aDataSource respondsToSelector:@selector(outlineView:numberOfChildrenOfItem:)]) 00224 [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:numberOfChildrenOfItem:'"]; 00225 00226 if (![aDataSource respondsToSelector:@selector(outlineView:objectValueForTableColumn:byItem:)]) 00227 [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:objectValueForTableColumn:byItem:'"]; 00228 00229 _outlineViewDataSource = aDataSource; 00230 _implementedOutlineViewDataSourceMethods = 0; 00231 00232 if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:setObjectValue:forTableColumn:byItem:)]) 00233 _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_setObjectValue_forTableColumn_byItem_; 00234 00235 if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:shouldDeferDisplayingChildrenOfItem:)]) 00236 _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_shouldDeferDisplayingChildrenOfItem_; 00237 00238 if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:acceptDrop:item:childIndex:)]) 00239 _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_acceptDrop_item_childIndex_; 00240 00241 if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:validateDrop:proposedItem:proposedChildIndex:)]) 00242 _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_validateDrop_proposedItem_proposedChildIndex_; 00243 00244 if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:validateDrop:proposedRow:proposedDropOperation:)]) 00245 _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_validateDrop_proposedRow_proposedDropOperation_; 00246 00247 if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:namesOfPromisedFilesDroppedAtDestination:forDraggedItems:)]) 00248 _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_namesOfPromisedFilesDroppedAtDestination_forDraggedItems_; 00249 00250 if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:itemForPersistentObject:)]) 00251 _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_itemForPersistentObject_; 00252 00253 if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:persistentObjectForItem:)]) 00254 _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_persistentObjectForItem_; 00255 00256 if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:writeItems:toPasteboard:)]) 00257 _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_writeItems_toPasteboard_; 00258 00259 if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:sortDescriptorsDidChange:)]) 00260 _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_sortDescriptorsDidChange_; 00261 00262 [self reloadData]; 00263 } 00264 00270 - (id)dataSource 00271 { 00272 return _outlineViewDataSource; 00273 } 00274 00282 - (BOOL)isExpandable:(id)anItem 00283 { 00284 if (!anItem) 00285 return YES; 00286 00287 var itemInfo = _itemInfosForItems[[anItem UID]]; 00288 00289 if (!itemInfo) 00290 return NO; 00291 00292 return itemInfo.isExpandable; 00293 } 00294 00302 - (BOOL)isItemExpanded:(id)anItem 00303 { 00304 if (!anItem) 00305 return YES; 00306 00307 var itemInfo = _itemInfosForItems[[anItem UID]]; 00308 00309 if (!itemInfo) 00310 return NO; 00311 00312 return itemInfo.isExpanded; 00313 } 00314 00320 - (void)expandItem:(id)anItem 00321 { 00322 [self expandItem:anItem expandChildren:NO]; 00323 } 00324 00331 - (void)expandItem:(id)anItem expandChildren:(BOOL)shouldExpandChildren 00332 { 00333 var itemInfo = null; 00334 00335 if (!anItem) 00336 itemInfo = _rootItemInfo; 00337 else 00338 itemInfo = _itemInfosForItems[[anItem UID]]; 00339 00340 if (!itemInfo) 00341 return; 00342 00343 // When shouldExpandChildren is YES, we need to make sure we're collecting 00344 // selection notifications so that exactly one IsChanging and one 00345 // DidChange is sent as needed, for the totality of the operation. 00346 var isTopLevel = NO; 00347 if (!_coalesceSelectionNotificationState) 00348 { 00349 isTopLevel = YES; 00350 _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOn; 00351 } 00352 00353 // To prevent items which are already expanded from firing notifications. 00354 if (!itemInfo.isExpanded) 00355 { 00356 [self _noteItemWillExpand:anItem]; 00357 00358 var previousRowCount = [self numberOfRows]; 00359 00360 itemInfo.isExpanded = YES; 00361 // XXX Shouldn't the items reload before the notification is sent? 00362 [self _noteItemDidExpand:anItem]; 00363 [self reloadItem:anItem reloadChildren:YES]; 00364 00365 // Shift selection indexes below so that the same items remain selected. 00366 var rowCountDelta = [self numberOfRows] - previousRowCount; 00367 if (rowCountDelta) 00368 { 00369 var selection = [self selectedRowIndexes], 00370 expandIndex = [self rowForItem:anItem] + 1; 00371 00372 if ([selection intersectsIndexesInRange:CPMakeRange(expandIndex, _itemsForRows.length)]) 00373 { 00374 [self _noteSelectionIsChanging]; 00375 [selection shiftIndexesStartingAtIndex:expandIndex by:rowCountDelta]; 00376 [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed. 00377 } 00378 } 00379 } 00380 00381 if (shouldExpandChildren) 00382 { 00383 var children = itemInfo.children, 00384 childIndex = children.length; 00385 00386 while (childIndex--) 00387 [self expandItem:children[childIndex] expandChildren:YES]; 00388 } 00389 00390 if (isTopLevel) 00391 { 00392 var r = _coalesceSelectionNotificationState; 00393 _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOff; 00394 if (r === CPOutlineViewCoalesceSelectionNotificationStateDid) 00395 [self _noteSelectionDidChange]; 00396 } 00397 } 00398 00404 - (void)collapseItem:(id)anItem 00405 { 00406 if (!anItem) 00407 return; 00408 00409 var itemInfo = _itemInfosForItems[[anItem UID]]; 00410 00411 if (!itemInfo) 00412 return; 00413 00414 if (!itemInfo.isExpanded) 00415 return; 00416 00417 // Don't spam notifications. 00418 _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOn; 00419 00420 [self _noteItemWillCollapse:anItem]; 00421 // Update selections: 00422 // * Deselect items inside the collapsed item. 00423 // * Shift row selections below the collapsed item so that the same logical items remain selected. 00424 var collapseTopIndex = [self rowForItem:anItem], 00425 topLevel = [self levelForRow:collapseTopIndex], 00426 collapseEndIndex = collapseTopIndex; 00427 00428 while (collapseEndIndex + 1 < _itemsForRows.length && [self levelForRow:collapseEndIndex + 1] > topLevel) 00429 collapseEndIndex++; 00430 00431 var collapseRange = CPMakeRange(collapseTopIndex + 1, collapseEndIndex - collapseTopIndex); 00432 00433 if (collapseRange.length) 00434 { 00435 var selection = [self selectedRowIndexes]; 00436 00437 if ([selection intersectsIndexesInRange:collapseRange]) 00438 { 00439 [self _noteSelectionIsChanging]; 00440 [selection removeIndexesInRange:collapseRange]; 00441 [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed. 00442 } 00443 00444 // Shift any selected rows below upwards. 00445 if ([selection intersectsIndexesInRange:CPMakeRange(collapseEndIndex + 1, _itemsForRows.length)]) 00446 { 00447 [self _noteSelectionIsChanging]; 00448 [selection shiftIndexesStartingAtIndex:collapseEndIndex + 1 by:-collapseRange.length]; 00449 [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed. 00450 } 00451 } 00452 itemInfo.isExpanded = NO; 00453 00454 // XXX Shouldn't the items reload before the notification is sent? 00455 [self _noteItemDidCollapse:anItem]; 00456 [self reloadItem:anItem reloadChildren:YES]; 00457 00458 // Send selection notifications only after the items have loaded so that 00459 // the new selection is consistent with the actual rows for any observers. 00460 var r = _coalesceSelectionNotificationState; 00461 _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOff; 00462 if (r === CPOutlineViewCoalesceSelectionNotificationStateDid) 00463 [self _noteSelectionDidChange]; 00464 } 00465 00471 - (void)reloadItem:(id)anItem 00472 { 00473 [self reloadItem:anItem reloadChildren:NO]; 00474 } 00475 00482 - (void)reloadItem:(id)anItem reloadChildren:(BOOL)shouldReloadChildren 00483 { 00484 if (!!shouldReloadChildren || !anItem) 00485 _loadItemInfoForItem(self, anItem); 00486 else 00487 _reloadItem(self, anItem); 00488 00489 [super reloadData]; 00490 } 00491 00498 - (id)itemAtRow:(CPInteger)aRow 00499 { 00500 return _itemsForRows[aRow] || nil; 00501 } 00502 00509 - (CPInteger)rowForItem:(id)anItem 00510 { 00511 if (!anItem) 00512 return _rootItemInfo.row; 00513 00514 var itemInfo = _itemInfosForItems[[anItem UID]]; 00515 00516 if (!itemInfo) 00517 return CPNotFound; 00518 00519 return itemInfo.row; 00520 } 00521 00528 - (void)setOutlineTableColumn:(CPTableColumn)aTableColumn 00529 { 00530 if (_outlineTableColumn === aTableColumn) 00531 return; 00532 00533 _outlineTableColumn = aTableColumn; 00534 00535 // FIXME: efficiency. 00536 [self reloadData]; 00537 } 00538 00544 - (CPTableColumn)outlineTableColumn 00545 { 00546 return _outlineTableColumn; 00547 } 00548 00557 - (CPInteger)levelForItem:(id)anItem 00558 { 00559 if (!anItem) 00560 return _rootItemInfo.level; 00561 00562 var itemInfo = _itemInfosForItems[[anItem UID]]; 00563 00564 if (!itemInfo) 00565 return CPNotFound; 00566 00567 return itemInfo.level; 00568 } 00569 00577 - (CPInteger)levelForRow:(CPInteger)aRow 00578 { 00579 return [self levelForItem:[self itemAtRow:aRow]]; 00580 } 00581 00587 - (void)setIndentationPerLevel:(float)anIndentationWidth 00588 { 00589 if (_indentationPerLevel === anIndentationWidth) 00590 return; 00591 00592 _indentationPerLevel = anIndentationWidth; 00593 00594 // FIXME: efficiency!!!! 00595 [self reloadData]; 00596 } 00597 00603 - (float)indentationPerLevel 00604 { 00605 return _indentationPerLevel; 00606 } 00607 00615 - (void)setIndentationMarkerFollowsDataView:(BOOL)indentationMarkerShouldFollowDataView 00616 { 00617 if (_indentationMarkerFollowsDataView === indentationMarkerShouldFollowDataView) 00618 return; 00619 00620 _indentationMarkerFollowsDataView = indentationMarkerShouldFollowDataView; 00621 00622 // !!!! 00623 [self reloadData]; 00624 } 00625 00633 - (BOOL)indentationMarkerFollowsDataView 00634 { 00635 return _indentationMarkerFollowsDataView; 00636 } 00637 00645 - (id)parentForItem:(id)anItem 00646 { 00647 if (!anItem) 00648 return nil; 00649 00650 var itemInfo = _itemInfosForItems[[anItem UID]]; 00651 00652 if (!itemInfo) 00653 return nil; 00654 00655 var parent = itemInfo.parent; 00656 00657 // Check if the parent is the root item because we never return the actual root item 00658 if (itemInfo[[parent UID]] === _rootItemInfo) 00659 parent = nil; 00660 00661 return parent; 00662 } 00663 00669 - (CGRect)_frameOfOutlineDataViewAtRow:(CPInteger)aRow 00670 { 00671 var columnIndex = [[self tableColumns] indexOfObject:_outlineTableColumn], 00672 frame = [super frameOfDataViewAtColumn:columnIndex row:aRow], 00673 indentationWidth = ([self levelForRow:aRow] + 1) * [self indentationPerLevel]; 00674 00675 frame.origin.x += indentationWidth; 00676 frame.size.width -= indentationWidth; 00677 00678 return frame; 00679 } 00680 00689 - (CGRect)frameOfOutlineDisclosureControlAtRow:(CPInteger)aRow 00690 { 00691 if (![self isExpandable:[self itemAtRow:aRow]]) 00692 return _CGRectMakeZero(); 00693 00694 var dataViewFrame = [self _frameOfOutlineDataViewAtRow:aRow], 00695 frame = _CGRectMake(_CGRectGetMinX(dataViewFrame) - 10, _CGRectGetMinY(dataViewFrame), 10, _CGRectGetHeight(dataViewFrame)); 00696 00697 return frame; 00698 } 00699 00704 - (void)_performSelection:(BOOL)select forRow:(CPInteger)rowIndex context:(id)context 00705 { 00706 [super _performSelection:select forRow:rowIndex context:context]; 00707 00708 var control = _disclosureControlsForRows[rowIndex], 00709 selector = select ? @"setThemeState:" : @"unsetThemeState:"; 00710 00711 [control performSelector:CPSelectorFromString(selector) withObject:CPThemeStateSelected]; 00712 } 00713 00790 - (void)setDelegate:(id)aDelegate 00791 { 00792 if (_outlineViewDelegate === aDelegate) 00793 return; 00794 00795 var defaultCenter = [CPNotificationCenter defaultCenter]; 00796 00797 if (_outlineViewDelegate) 00798 { 00799 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidMove:)]) 00800 [defaultCenter 00801 removeObserver:_outlineViewDelegate 00802 name:CPOutlineViewColumnDidMoveNotification 00803 object:self]; 00804 00805 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidResize:)]) 00806 [defaultCenter 00807 removeObserver:_outlineViewDelegate 00808 name:CPOutlineViewColumnDidResizeNotification 00809 object:self]; 00810 00811 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionDidChange:)]) 00812 [defaultCenter 00813 removeObserver:_outlineViewDelegate 00814 name:CPOutlineViewSelectionDidChangeNotification 00815 object:self]; 00816 00817 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionIsChanging:)]) 00818 [defaultCenter 00819 removeObserver:_outlineViewDelegate 00820 name:CPOutlineViewSelectionIsChangingNotification 00821 object:self]; 00822 00823 00824 00825 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillExpand:)]) 00826 [defaultCenter 00827 removeObserver:_outlineViewDelegate 00828 name:CPOutlineViewItemWillExpandNotification 00829 object:self]; 00830 00831 00832 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidExpand:)]) 00833 [defaultCenter 00834 removeObserver:_outlineViewDelegate 00835 name:CPOutlineViewItemDidExpandNotification 00836 object:self]; 00837 00838 00839 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillCollapse:)]) 00840 [defaultCenter 00841 removeObserver:_outlineViewDelegate 00842 name:CPOutlineViewItemWillCollapseNotification 00843 object:self]; 00844 00845 00846 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidCollapse:)]) 00847 [defaultCenter 00848 removeObserver:_outlineViewDelegate 00849 name:CPOutlineViewItemDidCollapseNotification 00850 object:self]; 00851 } 00852 00853 _outlineViewDelegate = aDelegate; 00854 _implementedOutlineViewDelegateMethods = 0; 00855 00856 var delegateMethods = [ 00857 CPOutlineViewDelegate_outlineView_dataViewForTableColumn_item_ , @selector(outlineView:dataViewForTableColumn:item:), 00858 CPOutlineViewDelegate_outlineView_didClickTableColumn_ , @selector(outlineView:didClickTableColumn:), 00859 CPOutlineViewDelegate_outlineView_didDragTableColumn_ , @selector(outlineView:didDragTableColumn:), 00860 CPOutlineViewDelegate_outlineView_heightOfRowByItem_ , @selector(outlineView:heightOfRowByItem:), 00861 CPOutlineViewDelegate_outlineView_isGroupItem_ , @selector(outlineView:isGroupItem:), 00862 CPOutlineViewDelegate_outlineView_mouseDownInHeaderOfTableColumn_ , @selector(outlineView:mouseDownInHeaderOfTableColumn:), 00863 CPOutlineViewDelegate_outlineView_nextTypeSelectMatchFromItem_toItem_forString_ , @selector(outlineView:nextTypeSelectMatchFromItem:toItem:forString:), 00864 CPOutlineViewDelegate_outlineView_selectionIndexesForProposedSelection_ , @selector(outlineView:selectionIndexesForProposedSelection:), 00865 CPOutlineViewDelegate_outlineView_shouldCollapseItem_ , @selector(outlineView:shouldCollapseItem:), 00866 CPOutlineViewDelegate_outlineView_shouldEditTableColumn_item_ , @selector(outlineView:shouldEditTableColumn:item:), 00867 CPOutlineViewDelegate_outlineView_shouldExpandItem_ , @selector(outlineView:shouldExpandItem:), 00868 CPOutlineViewDelegate_outlineView_shouldReorderColumn_toColumn_ , @selector(outlineView:shouldReorderColumn:toColumn:), 00869 CPOutlineViewDelegate_outlineView_shouldSelectItem_ , @selector(outlineView:shouldSelectItem:), 00870 CPOutlineViewDelegate_outlineView_shouldSelectTableColumn_ , @selector(outlineView:shouldSelectTableColumn:), 00871 CPOutlineViewDelegate_outlineView_shouldShowOutlineViewForItem_ , @selector(outlineView:shouldShowOutlineViewForItem:), 00872 CPOutlineViewDelegate_outlineView_shouldShowViewExpansionForTableColumn_item_ , @selector(outlineView:shouldShowViewExpansionForTableColumn:item:), 00873 CPOutlineViewDelegate_outlineView_shouldTrackView_forTableColumn_item_ , @selector(outlineView:shouldTrackView:forTableColumn:item:), 00874 CPOutlineViewDelegate_outlineView_shouldTypeSelectForEvent_withCurrentSearchString_ , @selector(outlineView:shouldTypeSelectForEvent:withCurrentSearchString:), 00875 CPOutlineViewDelegate_outlineView_sizeToFitWidthOfColumn_ , @selector(outlineView:sizeToFitWidthOfColumn:), 00876 CPOutlineViewDelegate_outlineView_toolTipForView_rect_tableColumn_item_mouseLocation_, @selector(outlineView:toolTipForView:rect:tableColumn:item:mouseLocation:), 00877 CPOutlineViewDelegate_outlineView_typeSelectStringForTableColumn_item_ , @selector(outlineView:typeSelectStringForTableColumn:item:), 00878 CPOutlineViewDelegate_outlineView_willDisplayOutlineView_forTableColumn_item_ , @selector(outlineView:willDisplayOutlineView:forTableColumn:item:), 00879 CPOutlineViewDelegate_outlineView_willDisplayView_forTableColumn_item_ , @selector(outlineView:willDisplayView:forTableColumn:item:), 00880 CPOutlineViewDelegate_selectionShouldChangeInOutlineView_ , @selector(selectionShouldChangeInOutlineView:), 00881 CPOutlineViewDelegate_outlineView_menuForTableColumn_item_ , @selector(outlineView:menuForTableColumn:item:) 00882 ], 00883 delegateCount = [delegateMethods count]; 00884 00885 for (var i = 0; i < delegateCount; i += 2) 00886 { 00887 var bitMask = delegateMethods[i], 00888 selector = delegateMethods[i + 1]; 00889 00890 if ([_outlineViewDelegate respondsToSelector:selector]) 00891 _implementedOutlineViewDelegateMethods |= bitMask; 00892 } 00893 00894 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidMove:)]) 00895 [defaultCenter 00896 addObserver:_outlineViewDelegate 00897 selector:@selector(outlineViewColumnDidMove:) 00898 name:CPOutlineViewColumnDidMoveNotification 00899 object:self]; 00900 00901 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidResize:)]) 00902 [defaultCenter 00903 addObserver:_outlineViewDelegate 00904 selector:@selector(outlineViewColumnDidMove:) 00905 name:CPOutlineViewColumnDidResizeNotification 00906 object:self]; 00907 00908 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionDidChange:)]) 00909 [defaultCenter 00910 addObserver:_outlineViewDelegate 00911 selector:@selector(outlineViewSelectionDidChange:) 00912 name:CPOutlineViewSelectionDidChangeNotification 00913 object:self]; 00914 00915 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionIsChanging:)]) 00916 [defaultCenter 00917 addObserver:_outlineViewDelegate 00918 selector:@selector(outlineViewSelectionIsChanging:) 00919 name:CPOutlineViewSelectionIsChangingNotification 00920 object:self]; 00921 00922 00923 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillExpand:)]) 00924 [defaultCenter 00925 addObserver:_outlineViewDelegate 00926 selector:@selector(outlineViewItemWillExpand:) 00927 name:CPOutlineViewItemWillExpandNotification 00928 object:self]; 00929 00930 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidExpand:)]) 00931 [defaultCenter 00932 addObserver:_outlineViewDelegate 00933 selector:@selector(outlineViewItemDidExpand:) 00934 name:CPOutlineViewItemDidExpandNotification 00935 object:self]; 00936 00937 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillCollapse:)]) 00938 [defaultCenter 00939 addObserver:_outlineViewDelegate 00940 selector:@selector(outlineViewItemWillCollapse:) 00941 name:CPOutlineViewItemWillCollapseNotification 00942 object:self]; 00943 00944 if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidCollapse:)]) 00945 [defaultCenter 00946 addObserver:_outlineViewDelegate 00947 selector:@selector(outlineViewItemDidCollapse:) 00948 name:CPOutlineViewItemDidCollapseNotification 00949 object:self]; 00950 00951 } 00952 00953 - (BOOL)_sendDelegateDeleteKeyPressed 00954 { 00955 if ([[self delegate] respondsToSelector: @selector(outlineViewDeleteKeyPressed:)]) 00956 { 00957 [[self delegate] outlineViewDeleteKeyPressed:self]; 00958 return YES; 00959 } 00960 00961 return NO; 00962 } 00963 00967 - (id)delegate 00968 { 00969 return _outlineViewDelegate; 00970 } 00971 00979 - (void)setDisclosureControlPrototype:(CPControl)aControl 00980 { 00981 _disclosureControlPrototype = aControl; 00982 _disclosureControlData = nil; 00983 _disclosureControlQueue = []; 00984 00985 // fIXME: really? 00986 [self reloadData]; 00987 } 00988 00992 - (void)reloadData 00993 { 00994 [self reloadItem:nil reloadChildren:YES]; 00995 } 00996 01007 - (void)addTableColumn:(CPTableColumn)aTableColumn 01008 { 01009 [super addTableColumn:aTableColumn]; 01010 01011 if ([self numberOfColumns] === 1) 01012 _outlineTableColumn = aTableColumn; 01013 } 01017 - (void)removeTableColumn:(CPTableColumn)aTableColumn 01018 { 01019 if (aTableColumn === [self outlineTableColumn]) 01020 CPLog("CPOutlineView cannot remove outlineTableColumn with removeTableColumn:. User setOutlineTableColumn: instead."); 01021 else 01022 [super removeTableColumn:aTableColumn]; 01023 } 01029 - (CGRect)frameOfDataViewAtColumn:(CPInteger)aColumn row:(CPInteger)aRow 01030 { 01031 var tableColumn = [self tableColumns][aColumn]; 01032 01033 if (tableColumn === _outlineTableColumn) 01034 return [self _frameOfOutlineDataViewAtRow:aRow]; 01035 01036 return [super frameOfDataViewAtColumn:aColumn row:aRow]; 01037 } 01038 01043 - (CPView)_dragViewForColumn:(int)theColumnIndex event:(CPEvent)theDragEvent offset:(CPPointPointer)theDragViewOffset 01044 { 01045 var dragView = [[_CPColumnDragView alloc] initWithLineColor:[self gridColor]], 01046 tableColumn = [[self tableColumns] objectAtIndex:theColumnIndex], 01047 bounds = _CGRectMake(0.0, 0.0, [tableColumn width], _CGRectGetHeight([self exposedRect]) + 23.0), 01048 columnRect = [self rectOfColumn:theColumnIndex], 01049 headerView = [tableColumn headerView], 01050 row = [_exposedRows firstIndex]; 01051 01052 while (row !== CPNotFound) 01053 { 01054 var dataView = [self _newDataViewForRow:row tableColumn:tableColumn], 01055 dataViewFrame = [self frameOfDataViewAtColumn:theColumnIndex row:row]; 01056 01057 // Only one column is ever dragged so we just place the view at 01058 dataViewFrame.origin.x = 0.0; 01059 01060 // Offset by table header height - scroll position 01061 dataViewFrame.origin.y = ( _CGRectGetMinY(dataViewFrame) - _CGRectGetMinY([self exposedRect]) ) + 23.0; 01062 [dataView setFrame:dataViewFrame]; 01063 01064 [dataView setObjectValue:[self _objectValueForTableColumn:tableColumn row:row]]; 01065 01066 01067 if (tableColumn === _outlineTableColumn) 01068 { 01069 // first inset the dragview 01070 var indentationWidth = ([self levelForRow:row] + 1) * [self indentationPerLevel]; 01071 01072 dataViewFrame.origin.x += indentationWidth; 01073 dataViewFrame.size.width -= indentationWidth; 01074 01075 [dataView setFrame:dataViewFrame]; 01076 } 01077 01078 [dragView addSubview:dataView]; 01079 01080 row = [_exposedRows indexGreaterThanIndex:row]; 01081 } 01082 01083 // Add the column header view 01084 var headerFrame = [headerView frame]; 01085 headerFrame.origin = _CGPointMakeZero(); 01086 01087 var columnHeaderView = [[_CPTableColumnHeaderView alloc] initWithFrame:headerFrame]; 01088 [columnHeaderView setStringValue:[headerView stringValue]]; 01089 [columnHeaderView setThemeState:[headerView themeState]]; 01090 [dragView addSubview:columnHeaderView]; 01091 01092 [dragView setBackgroundColor:[CPColor whiteColor]]; 01093 [dragView setAlphaValue:0.7]; 01094 [dragView setFrame:bounds]; 01095 01096 return dragView; 01097 } 01098 01114 - (void)setDropItem:(id)theItem dropChildIndex:(int)theIndex 01115 { 01116 if (_dropItem !== theItem && theIndex < 0 && [self isExpandable:theItem] && ![self isItemExpanded:theItem]) 01117 { 01118 if (_dragHoverTimer) 01119 [_dragHoverTimer invalidate]; 01120 01121 var autoExpandCallBack = function(){ 01122 if (_dropItem) 01123 { 01124 [_dropOperationFeedbackView blink]; 01125 [CPTimer scheduledTimerWithTimeInterval:.3 callback:objj_msgSend(self, "expandItem:", _dropItem) repeats:NO]; 01126 } 01127 } 01128 01129 _dragHoverTimer = [CPTimer scheduledTimerWithTimeInterval:.8 callback:autoExpandCallBack repeats:NO]; 01130 } 01131 01132 if (theIndex >= 0) 01133 { 01134 [_dragHoverTimer invalidate]; 01135 _dragHoverTimer = nil; 01136 } 01137 01138 _dropItem = theItem; 01139 _retargetedItem = theItem; 01140 _shouldRetargetItem = YES; 01141 01142 _retargedChildIndex = theIndex; 01143 _shouldRetargetChildIndex = YES; 01144 01145 // set CPTableView's _retargetedDropRow based on retargetedItem and retargetedChildIndex 01146 var retargetedItemInfo = (_retargetedItem !== nil) ? _itemInfosForItems[[_retargetedItem UID]] : _rootItemInfo; 01147 01148 if (_retargedChildIndex === [retargetedItemInfo.children count]) 01149 { 01150 var retargetedChildItem = [retargetedItemInfo.children lastObject]; 01151 _retargetedDropRow = [self rowForItem:retargetedChildItem] + 1; 01152 } 01153 else 01154 { 01155 var retargetedChildItem = (_retargedChildIndex !== CPOutlineViewDropOnItemIndex) ? retargetedItemInfo.children[_retargedChildIndex] : _retargetedItem; 01156 _retargetedDropRow = [self rowForItem:retargetedChildItem]; 01157 } 01158 } 01159 01163 - (void)_draggingEnded 01164 { 01165 [super _draggingEnded]; 01166 _dropItem = nil; 01167 [_dragHoverTimer invalidate]; 01168 _dragHoverTimer = nil; 01169 } 01170 01174 - (id)_parentItemForUpperRow:(int)theUpperRowIndex andLowerRow:(int)theLowerRowIndex atMouseOffset:(CPPoint)theOffset 01175 { 01176 if (_shouldRetargetItem) 01177 return _retargetedItem; 01178 01179 var lowerLevel = [self levelForRow:theLowerRowIndex], 01180 upperItem = [self itemAtRow:theUpperRowIndex], 01181 upperLevel = [self levelForItem:upperItem]; 01182 01183 // If the row above us has a higher level the item can be added to multiple parent items 01184 // Determine which one by looping through all possible parents and return the first 01185 // of which the indentation level is larger than the current x offset 01186 while (upperLevel > lowerLevel) 01187 { 01188 upperLevel = [self levelForItem:upperItem]; 01189 01190 // See if this item's indentation level matches the mouse offset 01191 if (theOffset.x > (upperLevel + 1) * [self indentationPerLevel]) 01192 return [self parentForItem:upperItem]; 01193 01194 // Check the next parent 01195 upperItem = [self parentForItem:upperItem]; 01196 } 01197 01198 return [self parentForItem:[self itemAtRow:theLowerRowIndex]]; 01199 } 01200 01204 - (CPRect)_rectForDropHighlightViewBetweenUpperRow:(int)theUpperRowIndex andLowerRow:(int)theLowerRowIndex offset:(CPPoint)theOffset 01205 { 01206 // Call super and the update x to reflect the current indentation level 01207 var rect = [super _rectForDropHighlightViewBetweenUpperRow:theUpperRowIndex andLowerRow:theLowerRowIndex offset:theOffset], 01208 parentItem = [self _parentItemForUpperRow:theUpperRowIndex andLowerRow:theLowerRowIndex atMouseOffset:theOffset], 01209 level = [self levelForItem:parentItem]; 01210 01211 rect.origin.x = (level + 1) * [self indentationPerLevel]; 01212 rect.size.width -= rect.origin.x; // This assumes that the x returned by super is zero 01213 01214 return rect; 01215 } 01216 01221 - (void)_layoutDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns 01222 { 01223 var rowArray = [], 01224 columnArray = []; 01225 01226 [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil]; 01227 [columns getIndexes:columnArray maxCount:-1 inIndexRange:nil]; 01228 01229 var columnIndex = 0, 01230 columnsCount = columnArray.length; 01231 01232 for (; columnIndex < columnsCount; ++columnIndex) 01233 { 01234 var column = columnArray[columnIndex], 01235 tableColumn = _tableColumns[column], 01236 tableColumnUID = [tableColumn UID], 01237 dataViewsForTableColumn = _dataViewsForTableColumns[tableColumnUID], 01238 rowIndex = 0, 01239 rowsCount = rowArray.length; 01240 01241 for (; rowIndex < rowsCount; ++rowIndex) 01242 { 01243 var row = rowArray[rowIndex], 01244 dataView = dataViewsForTableColumn[row], 01245 dataViewFrame = [self frameOfDataViewAtColumn:column row:row]; 01246 01247 [dataView setFrame:dataViewFrame]; 01248 01249 if (tableColumn === _outlineTableColumn) 01250 { 01251 var control = _disclosureControlsForRows[row], 01252 frame = [self frameOfOutlineDisclosureControlAtRow:row]; 01253 01254 [control setFrame:frame]; 01255 } 01256 } 01257 } 01258 } 01259 01263 - (void)_loadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns 01264 { 01265 [super _loadDataViewsInRows:rows columns:columns]; 01266 01267 var outlineColumn = [[self tableColumns] indexOfObjectIdenticalTo:[self outlineTableColumn]]; 01268 01269 if (![columns containsIndex:outlineColumn] || [self outlineTableColumn] === _draggedColumn) 01270 return; 01271 01272 var rowArray = []; 01273 01274 [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil]; 01275 01276 var rowIndex = 0, 01277 rowsCount = rowArray.length; 01278 01279 for (; rowIndex < rowsCount; ++rowIndex) 01280 { 01281 var row = rowArray[rowIndex], 01282 item = _itemsForRows[row], 01283 isExpandable = [self isExpandable:item]; 01284 01285 if (!isExpandable) 01286 continue; 01287 01288 var control = [self _dequeueDisclosureControl]; 01289 01290 _disclosureControlsForRows[row] = control; 01291 01292 [control setState:[self isItemExpanded:item] ? CPOnState : CPOffState]; 01293 var selector = [self isRowSelected:row] ? @"setThemeState:" : @"unsetThemeState:"; 01294 [control performSelector:CPSelectorFromString(selector) withObject:CPThemeStateSelected]; 01295 [control setFrame:[self frameOfOutlineDisclosureControlAtRow:row]]; 01296 01297 [self addSubview:control]; 01298 } 01299 } 01300 01304 - (void)_unloadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns 01305 { 01306 [super _unloadDataViewsInRows:rows columns:columns]; 01307 01308 var outlineColumn = [[self tableColumns] indexOfObjectIdenticalTo:[self outlineTableColumn]]; 01309 01310 if (![columns containsIndex:outlineColumn]) 01311 return; 01312 01313 var rowArray = []; 01314 01315 [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil]; 01316 01317 var rowIndex = 0, 01318 rowsCount = rowArray.length; 01319 01320 for (; rowIndex < rowsCount; ++rowIndex) 01321 { 01322 var row = rowArray[rowIndex], 01323 control = _disclosureControlsForRows[row]; 01324 01325 if (!control) 01326 continue; 01327 01328 [control removeFromSuperview]; 01329 01330 [self _enqueueDisclosureControl:control]; 01331 01332 _disclosureControlsForRows[row] = nil; 01333 } 01334 } 01335 01339 - (void)_toggleFromDisclosureControl:(CPControl)aControl 01340 { 01341 var controlFrame = [aControl frame], 01342 item = [self itemAtRow:[self rowAtPoint:_CGPointMake(_CGRectGetMinX(controlFrame), _CGRectGetMidY(controlFrame))]]; 01343 01344 if ([self isItemExpanded:item]) 01345 [self collapseItem:item]; 01346 01347 else 01348 [self expandItem:item]; 01349 } 01350 01354 - (void)_enqueueDisclosureControl:(CPControl)aControl 01355 { 01356 _disclosureControlQueue.push(aControl); 01357 } 01358 01362 - (CPControl)_dequeueDisclosureControl 01363 { 01364 if (_disclosureControlQueue.length) 01365 return _disclosureControlQueue.pop(); 01366 01367 if (!_disclosureControlData) 01368 if (!_disclosureControlPrototype) 01369 return nil; 01370 else 01371 _disclosureControlData = [CPKeyedArchiver archivedDataWithRootObject:_disclosureControlPrototype]; 01372 01373 var disclosureControl = [CPKeyedUnarchiver unarchiveObjectWithData:_disclosureControlData]; 01374 01375 [disclosureControl setTarget:self]; 01376 [disclosureControl setAction:@selector(_toggleFromDisclosureControl:)]; 01377 01378 return disclosureControl; 01379 } 01380 01384 - (void)_noteSelectionIsChanging 01385 { 01386 if (!_coalesceSelectionNotificationState || _coalesceSelectionNotificationState === CPOutlineViewCoalesceSelectionNotificationStateOn) 01387 { 01388 [[CPNotificationCenter defaultCenter] 01389 postNotificationName:CPOutlineViewSelectionIsChangingNotification 01390 object:self 01391 userInfo:nil]; 01392 } 01393 01394 if (_coalesceSelectionNotificationState === CPOutlineViewCoalesceSelectionNotificationStateOn) 01395 _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateDid; 01396 } 01397 01401 - (void)_noteSelectionDidChange 01402 { 01403 if (!_coalesceSelectionNotificationState) 01404 { 01405 [[CPNotificationCenter defaultCenter] 01406 postNotificationName:CPOutlineViewSelectionDidChangeNotification 01407 object:self 01408 userInfo:nil]; 01409 } 01410 01411 if (_coalesceSelectionNotificationState === CPOutlineViewCoalesceSelectionNotificationStateOn) 01412 _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateDid; 01413 } 01414 01418 - (void)_noteItemWillExpand:(id)item 01419 { 01420 [[CPNotificationCenter defaultCenter] 01421 postNotificationName:CPOutlineViewItemWillExpandNotification 01422 object:self 01423 userInfo:[CPDictionary dictionaryWithObject:item forKey:"CPObject"]]; 01424 } 01425 01429 - (void)_noteItemDidExpand:(id)item 01430 { 01431 [[CPNotificationCenter defaultCenter] 01432 postNotificationName:CPOutlineViewItemDidExpandNotification 01433 object:self 01434 userInfo:[CPDictionary dictionaryWithObject:item forKey:"CPObject"]]; 01435 } 01436 01440 - (void)_noteItemWillCollapse:(id)item 01441 { 01442 [[CPNotificationCenter defaultCenter] 01443 postNotificationName:CPOutlineViewItemWillCollapseNotification 01444 object:self 01445 userInfo:[CPDictionary dictionaryWithObject:item forKey:"CPObject"]]; 01446 } 01447 01451 - (void)_noteItemDidCollapse:(id)item 01452 { 01453 [[CPNotificationCenter defaultCenter] 01454 postNotificationName:CPOutlineViewItemDidCollapseNotification 01455 object:self 01456 userInfo:[CPDictionary dictionaryWithObject:item forKey:"CPObject"]]; 01457 } 01458 01459 01460 - (void)keyDown:(CPEvent)anEvent 01461 { 01462 var character = [anEvent charactersIgnoringModifiers], 01463 modifierFlags = [anEvent modifierFlags]; 01464 01465 // Check for the key events manually, as opposed to waiting for CPWindow to sent the actual action message 01466 // in _processKeyboardUIKey:, because we might not want to handle the arrow events. 01467 01468 if (character !== CPRightArrowFunctionKey && character !== CPLeftArrowFunctionKey) 01469 return [super keyDown:anEvent]; 01470 01471 var rows = [self selectedRowIndexes], 01472 indexes = [], 01473 items = []; 01474 01475 [rows getIndexes:indexes maxCount:-1 inIndexRange:nil]; 01476 01477 var i = 0, 01478 c = [indexes count]; 01479 01480 for (; i < c; i++) 01481 items.push([self itemAtRow:indexes[i]]); 01482 01483 01484 if (character === CPRightArrowFunctionKey) 01485 { 01486 for (var i = 0; i < c; i++) 01487 [self expandItem:items[i]]; 01488 } 01489 else if (character === CPLeftArrowFunctionKey) 01490 { 01491 for (var i = 0; i < c; i++) 01492 [self collapseItem:items[i]]; 01493 } 01494 01495 [super keyDown:anEvent]; 01496 } 01497 @end 01498 01499 // FIX ME: We're using with() here because Safari fails if we use anOutlineView._itemInfosForItems or whatever... 01500 var _reloadItem = function(/*CPOutlineView*/ anOutlineView, /*id*/ anItem) 01501 { 01502 if (!anItem) 01503 return; 01504 01505 with (anOutlineView) 01506 { 01507 // Get the existing info if it exists. 01508 var itemInfosForItems = _itemInfosForItems, 01509 dataSource = _outlineViewDataSource, 01510 itemUID = [anItem UID], 01511 itemInfo = itemInfosForItems[itemUID]; 01512 01513 // If we're not in the tree, then just bail. 01514 if (!itemInfo) 01515 return []; 01516 01517 // See if the item itself can be swapped out. 01518 var parent = itemInfo.parent, 01519 parentItemInfo = parent ? itemInfosForItems[[parent UID]] : _rootItemInfo, 01520 parentChildren = parentItemInfo.children, 01521 index = [parentChildren indexOfObjectIdenticalTo:anItem], 01522 newItem = [dataSource outlineView:anOutlineView child:index ofItem:parent]; 01523 01524 if (anItem !== newItem) 01525 { 01526 itemInfosForItems[[anItem UID]] = nil; 01527 itemInfosForItems[[newItem UID]] = itemInfo; 01528 01529 parentChildren[index] = newItem; 01530 _itemsForRows[itemInfo.row] = newItem; 01531 } 01532 01533 itemInfo.isExpandable = [dataSource outlineView:anOutlineView isItemExpandable:newItem]; 01534 itemInfo.isExpanded = itemInfo.isExpandable && itemInfo.isExpanded; 01535 } 01536 } 01537 01538 // FIX ME: We're using with() here because Safari fails if we use anOutlineView._itemInfosForItems or whatever... 01539 var _loadItemInfoForItem = function(/*CPOutlineView*/ anOutlineView, /*id*/ anItem, /*BOOL*/ isIntermediate) 01540 { 01541 with (anOutlineView) 01542 { 01543 var itemInfosForItems = _itemInfosForItems, 01544 dataSource = _outlineViewDataSource; 01545 01546 if (!anItem) 01547 var itemInfo = _rootItemInfo; 01548 01549 else 01550 { 01551 // Get the existing info if it exists. 01552 var itemUID = [anItem UID], 01553 itemInfo = itemInfosForItems[itemUID]; 01554 01555 // If we're not in the tree, then just bail. 01556 if (!itemInfo) 01557 return []; 01558 01559 itemInfo.isExpandable = [dataSource outlineView:anOutlineView isItemExpandable:anItem]; 01560 01561 // If we were previously expanded, but now no longer expandable, "de-expand". 01562 // NOTE: we are *not* collapsing, thus no notification is posted. 01563 if (!itemInfo.isExpandable && itemInfo.isExpanded) 01564 { 01565 itemInfo.isExpanded = NO; 01566 itemInfo.children = []; 01567 } 01568 } 01569 01570 // The root item does not count as a descendant. 01571 var weight = itemInfo.weight, 01572 descendants = anItem ? [anItem] : []; 01573 01574 if (itemInfo.isExpanded && (!(_implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_shouldDeferDisplayingChildrenOfItem_) || 01575 ![dataSource outlineView:anOutlineView shouldDeferDisplayingChildrenOfItem:anItem])) 01576 { 01577 var index = 0, 01578 count = [dataSource outlineView:anOutlineView numberOfChildrenOfItem:anItem], 01579 level = itemInfo.level + 1; 01580 01581 itemInfo.children = []; 01582 01583 for (; index < count; ++index) 01584 { 01585 var childItem = [dataSource outlineView:anOutlineView child:index ofItem:anItem], 01586 childItemInfo = itemInfosForItems[[childItem UID]]; 01587 01588 if (!childItemInfo) 01589 { 01590 childItemInfo = { isExpanded:NO, isExpandable:NO, children:[], weight:1 }; 01591 itemInfosForItems[[childItem UID]] = childItemInfo; 01592 } 01593 01594 itemInfo.children[index] = childItem; 01595 01596 var childDescendants = _loadItemInfoForItem(anOutlineView, childItem, YES); 01597 01598 childItemInfo.parent = anItem; 01599 childItemInfo.level = level; 01600 descendants = descendants.concat(childDescendants); 01601 } 01602 } 01603 01604 itemInfo.weight = descendants.length; 01605 01606 if (!isIntermediate) 01607 { 01608 // row = -1 is the root item, so just go to row 0 since it is ignored. 01609 var index = MAX(itemInfo.row, 0), 01610 itemsForRows = _itemsForRows; 01611 01612 descendants.unshift(index, weight); 01613 01614 itemsForRows.splice.apply(itemsForRows, descendants); 01615 01616 var count = itemsForRows.length; 01617 01618 for (; index < count; ++index) 01619 itemInfosForItems[[itemsForRows[index] UID]].row = index; 01620 01621 var deltaWeight = itemInfo.weight - weight; 01622 01623 if (deltaWeight !== 0) 01624 { 01625 var parent = itemInfo.parent; 01626 01627 while (parent) 01628 { 01629 var parentItemInfo = itemInfosForItems[[parent UID]]; 01630 01631 parentItemInfo.weight += deltaWeight; 01632 parent = parentItemInfo.parent; 01633 } 01634 01635 if (anItem) 01636 _rootItemInfo.weight += deltaWeight; 01637 } 01638 } 01639 }//end of with 01640 return descendants; 01641 } 01642 01643 @implementation _CPOutlineViewTableViewDataSource : CPObject 01644 { 01645 CPObject _outlineView; 01646 } 01647 01648 - (id)initWithOutlineView:(CPOutlineView)anOutlineView 01649 { 01650 self = [super init]; 01651 01652 if (self) 01653 _outlineView = anOutlineView; 01654 01655 return self; 01656 } 01657 01658 - (CPInteger)numberOfRowsInTableView:(CPTableView)anOutlineView 01659 { 01660 return _outlineView._itemsForRows.length; 01661 } 01662 01663 - (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow 01664 { 01665 return [_outlineView._outlineViewDataSource outlineView:_outlineView objectValueForTableColumn:aTableColumn byItem:_outlineView._itemsForRows[aRow]]; 01666 } 01667 01668 - (void)tableView:(CPTableView)aTableView setObjectValue:(id)aValue forTableColumn:(CPTableColumn)aColumn row:(CPInteger)aRow 01669 { 01670 if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_setObjectValue_forTableColumn_byItem_)) 01671 return; 01672 [_outlineView._outlineViewDataSource outlineView:_outlineView setObjectValue:aValue forTableColumn:aColumn byItem:_outlineView._itemsForRows[aRow]]; 01673 } 01674 01675 - (BOOL)tableView:(CPTableView)aTableColumn writeRowsWithIndexes:(CPIndexSet)theIndexes toPasteboard:(CPPasteboard)thePasteboard 01676 { 01677 if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_writeItems_toPasteboard_)) 01678 return NO; 01679 01680 var items = [], 01681 index = [theIndexes firstIndex]; 01682 01683 while (index !== CPNotFound) 01684 { 01685 [items addObject:[_outlineView itemAtRow:index]] 01686 index = [theIndexes indexGreaterThanIndex:index]; 01687 } 01688 01689 return [_outlineView._outlineViewDataSource outlineView:_outlineView writeItems:items toPasteboard:thePasteboard]; 01690 } 01691 01692 - (int)_childIndexForDropOperation:(CPTableViewDropOperation)theDropOperation row:(int)theRow offset:(CPPoint)theOffset 01693 { 01694 if (_outlineView._shouldRetargetChildIndex) 01695 return _outlineView._retargedChildIndex; 01696 01697 var childIndex = CPNotFound; 01698 01699 if (theDropOperation === CPTableViewDropAbove) 01700 { 01701 var parentItem = [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset], 01702 itemInfo = (parentItem !== nil) ? _outlineView._itemInfosForItems[[parentItem UID]] : _outlineView._rootItemInfo, 01703 children = itemInfo.children; 01704 01705 childIndex = [children indexOfObject:[_outlineView itemAtRow:theRow]]; 01706 01707 if (childIndex === CPNotFound) 01708 childIndex = children.length; 01709 } 01710 else if (theDropOperation === CPTableViewDropOn) 01711 childIndex = -1; 01712 01713 return childIndex; 01714 } 01715 01716 - (void)_parentItemForDropOperation:(CPTableViewDropOperation)theDropOperation row:(int)theRow offset:(CPPoint)theOffset 01717 { 01718 if (theDropOperation === CPTableViewDropAbove) 01719 return [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset] 01720 01721 return [_outlineView itemAtRow:theRow]; 01722 } 01723 01724 - (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(id < CPDraggingInfo >)theInfo 01725 proposedRow:(int)theRow proposedDropOperation:(CPTableViewDropOperation)theOperation 01726 { 01727 if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_validateDrop_proposedItem_proposedChildIndex_)) 01728 return CPDragOperationNone; 01729 01730 // Make sure the retargeted item and index are reset 01731 _outlineView._retargetedItem = nil; 01732 _outlineView._shouldRetargetItem = NO; 01733 01734 _outlineView._retargedChildIndex = nil; 01735 _outlineView._shouldRetargetChildIndex = NO; 01736 01737 var location = [_outlineView convertPoint:[theInfo draggingLocation] fromView:nil], 01738 parentItem = [self _parentItemForDropOperation:theOperation row:theRow offset:location], 01739 childIndex = [self _childIndexForDropOperation:theOperation row:theRow offset:location]; 01740 01741 return [_outlineView._outlineViewDataSource outlineView:_outlineView validateDrop:theInfo proposedItem:parentItem proposedChildIndex:childIndex]; 01742 } 01743 01744 - (BOOL)tableView:(CPTableView)aTableView acceptDrop:(id <CPDraggingInfo>)theInfo row:(int)theRow dropOperation:(CPTableViewDropOperation)theOperation 01745 { 01746 if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_acceptDrop_item_childIndex_)) 01747 return NO; 01748 01749 var location = [_outlineView convertPoint:[theInfo draggingLocation] fromView:nil], 01750 parentItem = [self _parentItemForDropOperation:theOperation row:theRow offset:location], 01751 childIndex = [self _childIndexForDropOperation:theOperation row:theRow offset:location]; 01752 01753 _outlineView._retargetedItem = nil; 01754 _outlineView._shouldRetargetItem = NO; 01755 01756 _outlineView._retargedChildIndex = nil; 01757 _outlineView._shouldRetargetChildIndex = NO; 01758 01759 return [_outlineView._outlineViewDataSource outlineView:_outlineView acceptDrop:theInfo item:parentItem childIndex:childIndex]; 01760 } 01761 01762 - (void)tableView:(CPTableView)aTableView sortDescriptorsDidChange:(CPArray)oldSortDescriptors 01763 { 01764 if ((_outlineView._implementedOutlineViewDataSourceMethods & 01765 CPOutlineViewDataSource_outlineView_sortDescriptorsDidChange_)) 01766 { 01767 [[_outlineView dataSource] outlineView:_outlineView sortDescriptorsDidChange:oldSortDescriptors]; 01768 } 01769 } 01770 01771 @end 01772 01773 @implementation _CPOutlineViewTableViewDelegate : CPObject 01774 { 01775 CPOutlineView _outlineView; 01776 } 01777 01778 - (id)initWithOutlineView:(CPOutlineView)anOutlineView 01779 { 01780 self = [super init]; 01781 01782 if (self) 01783 _outlineView = anOutlineView; 01784 01785 return self; 01786 } 01787 01788 - (CPView)tableView:(CPTableView)theTableView dataViewForTableColumn:(CPTableColumn)theTableColumn row:(int)theRow 01789 { 01790 var dataView = nil; 01791 01792 if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_dataViewForTableColumn_item_)) 01793 dataView = [_outlineView._outlineViewDelegate outlineView:_outlineView 01794 dataViewForTableColumn:theTableColumn 01795 item:[_outlineView itemAtRow:theRow]]; 01796 01797 if (!dataView) 01798 dataView = [theTableColumn dataViewForRow:theRow]; 01799 01800 return dataView; 01801 } 01802 01803 - (BOOL)tableView:(CPTableView)theTableView shouldSelectRow:(int)theRow 01804 { 01805 if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectItem_)) 01806 return [_outlineView._outlineViewDelegate outlineView:_outlineView shouldSelectItem:[_outlineView itemAtRow:theRow]]; 01807 01808 return YES; 01809 } 01810 01811 - (BOOL)tableView:(CPTableView)aTableView shouldEditTableColumn:(CPTableColumn)aColumn row:(int)aRow 01812 { 01813 if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldEditTableColumn_item_)) 01814 return [_outlineView._outlineViewDelegate outlineView:_outlineView shouldEditTableColumn:aColumn item:[_outlineView itemAtRow:aRow]]; 01815 01816 return NO; 01817 } 01818 01819 - (float)tableView:(CPTableView)theTableView heightOfRow:(int)theRow 01820 { 01821 if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_heightOfRowByItem_)) 01822 return [_outlineView._outlineViewDelegate outlineView:_outlineView heightOfRowByItem:[_outlineView itemAtRow:theRow]]; 01823 01824 return [theTableView rowHeight]; 01825 } 01826 01827 - (void)tableView:(CPTableView)aTableView willDisplayView:(id)aView forTableColumn:(CPTableColumn)aTableColumn row:(int)aRowIndex 01828 { 01829 if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_willDisplayView_forTableColumn_item_)) 01830 { 01831 var item = [_outlineView itemAtRow:aRowIndex]; 01832 [_outlineView._outlineViewDelegate outlineView:_outlineView willDisplayView:aView forTableColumn:aTableColumn item:item]; 01833 } 01834 } 01835 01836 - (BOOL)tableView:(CPTableView)aTableView isGroupRow:(int)aRow 01837 { 01838 if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_isGroupItem_)) 01839 return [_outlineView._outlineViewDelegate outlineView:_outlineView isGroupItem:[_outlineView itemAtRow:aRow]]; 01840 01841 return NO; 01842 } 01843 01844 - (CPMenu)tableView:(CPTableView)aTableView menuForTableColumn:(CPTableColumn)aTableColumn row:(int)aRow 01845 { 01846 if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_menuForTableColumn_item_)) 01847 { 01848 var item = [_outlineView itemAtRow:aRow]; 01849 return [_outlineView._outlineViewDelegate outlineView:_outlineView menuForTableColumn:aTableColumn item:item] 01850 } 01851 01852 return nil; 01853 } 01854 01855 @end 01856 01857 @implementation CPDisclosureButton : CPButton 01858 { 01859 float _angle; 01860 } 01861 01862 - (id)initWithFrame:(CGRect)aFrame 01863 { 01864 self = [super initWithFrame:aFrame]; 01865 01866 if (self) 01867 [self setBordered:NO]; 01868 01869 return self; 01870 } 01871 01872 - (void)setState:(CPState)aState 01873 { 01874 [super setState:aState]; 01875 01876 if ([self state] === CPOnState) 01877 _angle = 0.0; 01878 01879 else 01880 _angle = -PI_2; 01881 } 01882 01883 - (void)drawRect:(CGRect)aRect 01884 { 01885 var bounds = [self bounds], 01886 context = [[CPGraphicsContext currentContext] graphicsPort]; 01887 01888 CGContextBeginPath(context); 01889 01890 CGContextTranslateCTM(context, _CGRectGetWidth(bounds) / 2.0, _CGRectGetHeight(bounds) / 2.0); 01891 CGContextRotateCTM(context, _angle); 01892 CGContextTranslateCTM(context, -_CGRectGetWidth(bounds) / 2.0, -_CGRectGetHeight(bounds) / 2.0); 01893 01894 // Center, but crisp. 01895 CGContextTranslateCTM(context, FLOOR((_CGRectGetWidth(bounds) - 9.0) / 2.0), FLOOR((_CGRectGetHeight(bounds) - 8.0) / 2.0)); 01896 01897 CGContextMoveToPoint(context, 0.0, 0.0); 01898 CGContextAddLineToPoint(context, 9.0, 0.0); 01899 CGContextAddLineToPoint(context, 4.5, 8.0); 01900 CGContextAddLineToPoint(context, 0.0, 0.0); 01901 01902 CGContextClosePath(context); 01903 CGContextSetFillColor(context, 01904 colorForDisclosureTriangle([self hasThemeState:CPThemeStateSelected], 01905 [self hasThemeState:CPThemeStateHighlighted])); 01906 CGContextFillPath(context); 01907 01908 01909 CGContextBeginPath(context); 01910 CGContextMoveToPoint(context, 0.0, 0.0); 01911 01912 if (_angle === 0.0) 01913 { 01914 CGContextAddLineToPoint(context, 4.5, 8.0); 01915 CGContextAddLineToPoint(context, 9.0, 0.0); 01916 } 01917 01918 else 01919 CGContextAddLineToPoint(context, 4.5, 8.0); 01920 01921 CGContextSetStrokeColor(context, [CPColor colorWithCalibratedWhite:1.0 alpha: 0.8]); 01922 CGContextStrokePath(context); 01923 } 01924 01925 @end 01926 01927 01928 var CPOutlineViewIndentationPerLevelKey = @"CPOutlineViewIndentationPerLevelKey", 01929 CPOutlineViewOutlineTableColumnKey = @"CPOutlineViewOutlineTableColumnKey", 01930 CPOutlineViewDataSourceKey = @"CPOutlineViewDataSourceKey", 01931 CPOutlineViewDelegateKey = @"CPOutlineViewDelegateKey"; 01932 01933 @implementation CPOutlineView (CPCoding) 01934 01935 - (id)initWithCoder:(CPCoder)aCoder 01936 { 01937 self = [super initWithCoder:aCoder]; 01938 01939 if (self) 01940 { 01941 // The root item has weight "0", thus represents the weight solely of its descendants. 01942 _rootItemInfo = { isExpanded:YES, isExpandable:NO, level:-1, row:-1, children:[], weight:0 }; 01943 01944 _itemsForRows = []; 01945 _itemInfosForItems = { }; 01946 _disclosureControlsForRows = []; 01947 01948 [self setIndentationMarkerFollowsDataView:YES]; 01949 [self setDisclosureControlPrototype:[[CPDisclosureButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 10.0)]]; 01950 01951 _outlineTableColumn = [aCoder decodeObjectForKey:CPOutlineViewOutlineTableColumnKey]; 01952 _indentationPerLevel = [aCoder decodeFloatForKey:CPOutlineViewIndentationPerLevelKey]; 01953 01954 _outlineViewDataSource = [aCoder decodeObjectForKey:CPOutlineViewDataSourceKey]; 01955 _outlineViewDelegate = [aCoder decodeObjectForKey:CPOutlineViewDelegateKey]; 01956 01957 [super setDataSource:[[_CPOutlineViewTableViewDataSource alloc] initWithOutlineView:self]]; 01958 [super setDelegate:[[_CPOutlineViewTableViewDelegate alloc] initWithOutlineView:self]]; 01959 } 01960 01961 return self; 01962 } 01963 01964 - (void)encodeWithCoder:(CPCoder)aCoder 01965 { 01966 // Make sure we don't encode our internal delegate and data source. 01967 var internalDelegate = _delegate, 01968 internalDataSource = _dataSource; 01969 _delegate = nil; 01970 _dataSource = nil; 01971 [super encodeWithCoder:aCoder]; 01972 _delegate = internalDelegate; 01973 _dataSource = internalDataSource; 01974 01975 [aCoder encodeObject:_outlineTableColumn forKey:CPOutlineViewOutlineTableColumnKey]; 01976 [aCoder encodeFloat:_indentationPerLevel forKey:CPOutlineViewIndentationPerLevelKey]; 01977 01978 [aCoder encodeObject:_outlineViewDataSource forKey:CPOutlineViewDataSourceKey]; 01979 [aCoder encodeObject:_outlineViewDelegate forKey:CPOutlineViewDelegateKey]; 01980 } 01981 01982 @end 01983 01984 01985 var colorForDisclosureTriangle = function(isSelected, isHighlighted) { 01986 return isSelected 01987 ? (isHighlighted 01988 ? [CPColor colorWithCalibratedWhite:0.9 alpha: 1.0] 01989 : [CPColor colorWithCalibratedWhite:1.0 alpha: 1.0]) 01990 : (isHighlighted 01991 ? [CPColor colorWithCalibratedWhite:0.4 alpha: 1.0] 01992 : [CPColor colorWithCalibratedWhite:0.5 alpha: 1.0]); 01993 }