diff --git a/AppKit/CPOutlineView.j b/AppKit/CPOutlineView.j index e1c5bad01..32ed10c1e 100644 --- a/AppKit/CPOutlineView.j +++ b/AppKit/CPOutlineView.j @@ -346,7 +346,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0, if (!anItem) return YES; - var itemInfo = _itemInfosForItems[[anItem UID]]; + var itemInfo = [self _itemInfosForItem:anItem]; if (!itemInfo) return NO; @@ -379,7 +379,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0, if (!anItem) return YES; - var itemInfo = _itemInfosForItems[[anItem UID]]; + var itemInfo = [self _itemInfosForItem:anItem]; if (!itemInfo) return NO; @@ -387,6 +387,107 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0, return itemInfo.isExpanded; } +/*! + Returns the item at a given row index. If no item exists nil is returned. + + @param aRow - The row index you want to find the item at. + @return id - The item at a given index. +*/ +- (id)itemAtRow:(CPInteger)aRow +{ + return _itemsForRows[aRow] || nil; +} + +/*! + Returns the row of a given item + + @param anItem - The item you want to find the row of. + @return int - The row index of a given item. +*/ +- (CPInteger)rowForItem:(id)anItem +{ + if (!anItem) + return _rootItemInfo.row; + + var itemInfo = [self _itemInfosForItem:anItem]; + + if (!itemInfo) + return CPNotFound; + + return itemInfo.row; +} + +/*! + Returns the indentation level of a given item. If the item is nil (the top + level root item) CPNotFound is returned. Indentation levels are zero + based, thus items that are not indented return 0. + + @param anItem - The item you want the indentation level for. + @return int - the indentation level of anItem. +*/ +- (CPInteger)levelForItem:(id)anItem +{ + if (!anItem) + return _rootItemInfo.level; + + var itemInfo = [self _itemInfosForItem:anItem]; + + if (!itemInfo) + return CPNotFound; + + return itemInfo.level; +} + +/*! + Returns the indentation level for a given row. If the row is invalid + CPNotFound is returned. Rows that are not indented return 0. + + @param aRow - the row of the receiver + @return int - the indentation level of aRow. +*/ +- (CPInteger)levelForRow:(CPInteger)aRow +{ + var item = [self itemAtRow:aRow]; + + if (!item && aRow >= 0) + item = [CPObject new]; + + return [self levelForItem:item]; +} + +/*! + @ignore + Return the itemInfos for the given item. + The method returns only itemInfo for displayed items +*/ +- (Object)_itemInfosForItem:(id)anItem +{ + var itemInfo = _itemInfosForItems[[anItem UID]]; + + if (!itemInfo || [self _parentIsCollapsed:[self parentForItem:anItem]]) + return nil; + + return itemInfo; +} + +/*! + @ignore + Return a boolean to know if one parent of the given item is collapsed or not +*/ +- (BOOL)_parentIsCollapsed:(id)anItem +{ + var parentItem = [self parentForItem:anItem], + itemInfo = _itemInfosForItems[[anItem UID]]; + + if (!itemInfo) + return NO; + + if (!itemInfo.isExpanded) + return YES; + + return [self _parentIsCollapsed:parentItem]; +} + /*! Expands a given item. @@ -423,6 +524,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0, // selection notifications so that exactly one IsChanging and one // DidChange is sent as needed, for the totality of the operation. var isTopLevel = NO; + if (!_coalesceSelectionNotificationState) { isTopLevel = YES; @@ -442,6 +544,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0, // Shift selection indexes below so that the same items remain selected. var rowCountDelta = [self numberOfRows] - previousRowCount; + if (rowCountDelta) { var selection = [self selectedRowIndexes], @@ -531,6 +634,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0, [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed. } } + itemInfo.isExpanded = NO; [self reloadItem:anItem reloadChildren:YES]; @@ -570,36 +674,6 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0, [super reloadData]; } -/*! - Returns the item at a given row index. If no item exists nil is returned. - - @param aRow - The row index you want to find the item at. - @return id - The item at a given index. -*/ -- (id)itemAtRow:(CPInteger)aRow -{ - return _itemsForRows[aRow] || nil; -} - -/*! - Returns the row of a given item - - @param anItem - The item you want to find the row of. - @return int - The row index of a given item. -*/ -- (CPInteger)rowForItem:(id)anItem -{ - if (!anItem) - return _rootItemInfo.row; - - var itemInfo = _itemInfosForItems[[anItem UID]]; - - if (!itemInfo) - return CPNotFound; - - return itemInfo.row; -} - /*! Sets the table column you want to display the disclosure button in. If you do not want an outline column pass nil. @@ -627,39 +701,6 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0, return _outlineTableColumn; } -/*! - Returns the indentation level of a given item. If the item is nil (the top - level root item) CPNotFound is returned. Indentation levels are zero - based, thus items that are not indented return 0. - - @param anItem - The item you want the indentation level for. - @return int - the indentation level of anItem. -*/ -- (CPInteger)levelForItem:(id)anItem -{ - if (!anItem) - return _rootItemInfo.level; - - var itemInfo = _itemInfosForItems[[anItem UID]]; - - if (!itemInfo) - return CPNotFound; - - return itemInfo.level; -} - -/*! - Returns the indentation level for a given row. If the row is invalid - CPNotFound is returned. Rows that are not indented return 0. - - @param aRow - the row of the receiver - @return int - the indentation level of aRow. -*/ -- (CPInteger)levelForRow:(CPInteger)aRow -{ - return [self levelForItem:[self itemAtRow:aRow]]; -} - /*! Sets the number of pixels to indent an item at each indentation level. @@ -770,6 +811,7 @@ var CPOutlineViewCoalesceSelectionNotificationStateOff = 0, - (CGRect)frameOfOutlineDisclosureControlAtRow:(CPInteger)aRow { var theItem = [self itemAtRow:aRow]; + if (![self isExpandable:theItem] || ![self _shouldShowOutlineDisclosureControlForItem:theItem]) return CGRectMakeZero(); @@ -1706,6 +1748,47 @@ var _reloadItem = function(/*CPOutlineView*/ anOutlineView, /*id*/ anItem) } }; +var _cleanPreviousItems = function(/*CPOutlineView*/ anOutlineView, /*id*/ itemInfo, /*Object*/previousItem) +{ + with (anOutlineView) + { + if (!itemInfo) + return; + + var children = itemInfo.children; + + for (var i = [previousItem count] - 1; i >= 0; i--) + { + var item = previousItem[i]; + + if (![children containsObject:item]) + _cleanItem(anOutlineView, item); + } + } +} + +var _cleanItem = function(/*CPOutlineView*/ anOutlineView, /*id*/ anItem) +{ + with (anOutlineView) + { + var itemInfo = _itemInfosForItems[[anItem UID]]; + + if (!itemInfo) + return; + + var children = itemInfo.children; + + for (var i = [children count]; i >= 0; i--) + { + var child = children[i]; + + _cleanItem(anOutlineView, child); + } + + delete _itemInfosForItems[[anItem UID]]; + } +} + // FIX ME: We're using with() here because Safari fails if we use anOutlineView._itemInfosForItems or whatever... var _loadItemInfoForItem = function(/*CPOutlineView*/ anOutlineView, /*id*/ anItem, /*BOOL*/ isIntermediate) { @@ -1715,8 +1798,9 @@ var _loadItemInfoForItem = function(/*CPOutlineView*/ anOutlineView, /*id*/ anIt dataSource = _outlineViewDataSource; if (!anItem) + { var itemInfo = _rootItemInfo; - + } else { // Get the existing info if it exists. @@ -1748,7 +1832,8 @@ var _loadItemInfoForItem = function(/*CPOutlineView*/ anOutlineView, /*id*/ anIt { var index = 0, count = [dataSource outlineView:anOutlineView numberOfChildrenOfItem:anItem], - level = itemInfo.level + 1; + level = itemInfo.level + 1, + previousChildren = itemInfo.children; itemInfo.children = []; @@ -1771,6 +1856,10 @@ var _loadItemInfoForItem = function(/*CPOutlineView*/ anOutlineView, /*id*/ anIt childItemInfo.level = level; descendants = descendants.concat(childDescendants); } + + // Here we clean the itemInfos dictionary + // Some items could have been removed at this point, we don't need to keep a ref of them anymore + _cleanPreviousItems(anOutlineView, itemInfo, previousChildren) } itemInfo.weight = descendants.length; @@ -1809,6 +1898,7 @@ var _loadItemInfoForItem = function(/*CPOutlineView*/ anOutlineView, /*id*/ anIt } } }//end of with + return descendants; }; diff --git a/Tests/AppKit/CPOutlineViewTest.j b/Tests/AppKit/CPOutlineViewTest.j index 37278a7fb..65219ea41 100644 --- a/Tests/AppKit/CPOutlineViewTest.j +++ b/Tests/AppKit/CPOutlineViewTest.j @@ -209,6 +209,68 @@ [self assertTrue:[outlineView isItemExpanded:".1"] message:".1 is still expanded, cannot collapse"]; } +- (void)testRowForItem +{ + [self assert:2 equals:[outlineView rowForItem:@".1.2"] message:".1.2 row number is wrong"]; + [self assert:6 equals:[outlineView rowForItem:@".3"] message:".3 row number is wrong"]; + [outlineView collapseItem:".1"]; + [self assert:CPNotFound equals:[outlineView rowForItem:@".1.2"] message:".1.2 row number is wrong"]; + [self assert:2 equals:[outlineView rowForItem:@".3"] message:".3 row number is wrong"]; + [outlineView expandItem:".1"]; + [self assert:2 equals:[outlineView rowForItem:@".1.2"] message:".1.2 row number is wrong"]; + [self assert:6 equals:[outlineView rowForItem:@".3"] message:".3 row is number wrong"]; +} + +- (void)testItemAtRow +{ + [self assert:@".1.2" equals:[outlineView itemAtRow:2] message:"itemAtRow 2 is wrong"]; + [self assert:@".3" equals:[outlineView itemAtRow:6] message:"itemAtRow 6 is wrong"]; + [self assert:nil equals:[outlineView itemAtRow:8] message:"itemAtRow 8 is wrong"]; + [outlineView collapseItem:".1"]; + [self assert:@".3" equals:[outlineView itemAtRow:2] message:"itemAtRow 2 is wrong"]; + [self assert:nil equals:[outlineView itemAtRow:5] message:"itemAtRow 5 is wrong"]; + [outlineView expandItem:".1"]; + [self assert:@".1.2" equals:[outlineView itemAtRow:2] message:"itemAtRow 2 is wrong"]; + [self assert:@".3" equals:[outlineView itemAtRow:6] message:"itemAtRow 6 is wrong"]; + [self assert:nil equals:[outlineView itemAtRow:8] message:".itemAtRow 8 is wrong"]; +} + +- (void)testItemIsExpanded +{ + [self assert:YES equals:[outlineView isItemExpanded:@".1.2"] message:".1.2 expanded value is wrong"]; + [self assert:YES equals:[outlineView isItemExpanded:@".3"] message:".3 expanded value is wrong"]; + [outlineView collapseItem:".1"]; + [self assert:NO equals:[outlineView isItemExpanded:@".1.2"] message:".1.2 expanded value is wrong"]; + [self assert:YES equals:[outlineView isItemExpanded:@".3"] message:".3 expanded value is wrong"]; + [outlineView expandItem:".1"]; + [self assert:YES equals:[outlineView isItemExpanded:@".1.2"] message:".1.2 expanded value is wrong"]; + [self assert:YES equals:[outlineView isItemExpanded:@".3"] message:".3 expanded value is wrong"]; +} + +- (void)testLevelForItem +{ + [self assert:1 equals:[outlineView levelForItem:@".1.2"] message:".1.2 level value is wrong"]; + [self assert:0 equals:[outlineView levelForItem:@".3"] message:".3 level value is wrong"]; + [outlineView collapseItem:".1"]; + [self assert:CPNotFound equals:[outlineView levelForItem:@".1.2"] message:".1.2 level value is wrong"]; + [self assert:0 equals:[outlineView levelForItem:@".3"] message:".3 level value is wrong"]; + [outlineView expandItem:".1"]; + [self assert:1 equals:[outlineView levelForItem:@".1.2"] message:".1.2 level value is wrong"]; + [self assert:0 equals:[outlineView levelForItem:@".3"] message:".3 level value is wrong"]; +} + +- (void)testLevelForRow +{ + [self assert:2 equals:[outlineView levelForRow:3] message:"levelForRow 2 is wrong"]; + [self assert:0 equals:[outlineView levelForRow:6] message:"levelForRow 6 is wrong"]; + [outlineView collapseItem:".1"]; + [self assert:0 equals:[outlineView levelForRow:2] message:"levelForRow 2 is wrong"]; + [self assert:CPNotFound equals:[outlineView levelForRow:4] message:"levelForRow 4 is wrong"]; + [outlineView expandItem:".1"]; + [self assert:2 equals:[outlineView levelForRow:3] message:"levelForRow 2 is wrong"]; + [self assert:0 equals:[outlineView levelForRow:6] message:"levelForRow 0 is wrong"]; +} + /*! Test that the outline view archives properly. */