mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-09-05 02:13:40 +00:00
Merge branch 'flllow-outlineview-selection'
This commit is contained in:
+68
-12
@@ -79,11 +79,11 @@ CPOutlineViewDropOnItemIndex = -1;
|
||||
@ingroup appkit
|
||||
@class CPOutlineView
|
||||
|
||||
CPOutlineView is a subclass of CPTableView that inherates the row and column format to display hierarchial data.
|
||||
CPOutlineView is a subclass of CPTableView that inherates the row and column format to display hierarchial data.
|
||||
The outlineview adds the ability to expand and collapse items. This is useful for browsing a tree like structure such as directories or a filesystem.
|
||||
|
||||
Like the tableview, an outlineview uses a data source to supply its data. For this reason you must implement a couple data source methods (documented in setDataSource:)
|
||||
|
||||
|
||||
*/
|
||||
@implementation CPOutlineView : CPTableView
|
||||
{
|
||||
@@ -151,7 +151,7 @@ CPOutlineViewDropOnItemIndex = -1;
|
||||
}
|
||||
/*!
|
||||
In addition to standard delegation, the outline view also supports data source delegation. This method sets the data source object.
|
||||
Just like the TableView you have CPTableColumns but instead of rows you deal with items.
|
||||
Just like the TableView you have CPTableColumns but instead of rows you deal with items.
|
||||
|
||||
You must implement these data source methods:
|
||||
|
||||
@@ -165,7 +165,7 @@ CPOutlineViewDropOnItemIndex = -1;
|
||||
Returns the number of child items of a given item. If item is nil you should return the number of top level (root) items.
|
||||
|
||||
- (id)outlineView:(CPOutlineView)outlineView objectValueForTableColumn:(CPTableColumn)tableColumn byItem:(id)item;
|
||||
Returns the object value of the item in a given column.
|
||||
Returns the object value of the item in a given column.
|
||||
|
||||
|
||||
The following methods are optional:
|
||||
@@ -195,7 +195,7 @@ CPOutlineViewDropOnItemIndex = -1;
|
||||
Returns YES if the drop operation is allowed otherwise NO.
|
||||
This method is invoked by the outlineview after a drag should begin, but before it is started. If you dont want the drag to being return NO.
|
||||
If you want the drag to begin you should return YES and place the drag data on the pboard.
|
||||
|
||||
|
||||
*/
|
||||
- (void)setDataSource:(id)aDataSource
|
||||
{
|
||||
@@ -280,11 +280,11 @@ CPOutlineViewDropOnItemIndex = -1;
|
||||
}
|
||||
|
||||
/*!
|
||||
Used to find if an item is already expanded.
|
||||
Used to find if an item is already expanded.
|
||||
|
||||
@param anItem - the item you are interest in.
|
||||
|
||||
@return BOOL - Yes if the item is already expanded, otherwise NO.
|
||||
@return BOOL - Yes if the item is already expanded, otherwise NO.
|
||||
*/
|
||||
- (BOOL)isItemExpanded:(id)anItem
|
||||
{
|
||||
@@ -331,6 +331,22 @@ CPOutlineViewDropOnItemIndex = -1;
|
||||
if (!itemInfo.isExpanded)
|
||||
{
|
||||
[self _noteItemWillExpand:anItem];
|
||||
|
||||
// Shift selection indexes below so that the same items remain selected.
|
||||
var newRowCount = [_outlineViewDataSource outlineView:self numberOfChildrenOfItem:anItem];
|
||||
if (newRowCount)
|
||||
{
|
||||
var selection = [self selectedRowIndexes],
|
||||
expandIndex = [self rowForItem:anItem] + 1;
|
||||
|
||||
if ([selection intersectsIndexesInRange:CPMakeRange(expandIndex, _itemsForRows.length)])
|
||||
{
|
||||
[self _noteSelectionIsChanging];
|
||||
[selection shiftIndexesStartingAtIndex:expandIndex by:newRowCount];
|
||||
[self _setSelectedRowIndexes:selection];
|
||||
}
|
||||
}
|
||||
|
||||
itemInfo.isExpanded = YES;
|
||||
[self _noteItemDidExpand:anItem];
|
||||
[self reloadItem:anItem reloadChildren:YES];
|
||||
@@ -365,7 +381,47 @@ CPOutlineViewDropOnItemIndex = -1;
|
||||
return;
|
||||
|
||||
[self _noteItemWillCollapse:anItem];
|
||||
// Update selections:
|
||||
// * Deselect items inside the collapsed item.
|
||||
// * Shift row selections below the collapsed item so that the same logical items remain selected.
|
||||
var collapseTopIndex = [self rowForItem:anItem],
|
||||
topLevel = [self levelForRow:collapseTopIndex],
|
||||
collapseEndIndex = collapseTopIndex;
|
||||
|
||||
while (collapseEndIndex + 1 < _itemsForRows.length && [self levelForRow:collapseEndIndex + 1] > topLevel)
|
||||
collapseEndIndex++;
|
||||
|
||||
var collapseRange = CPMakeRange(collapseTopIndex + 1, collapseEndIndex - collapseTopIndex);
|
||||
if (collapseRange.length)
|
||||
{
|
||||
var selection = [self selectedRowIndexes],
|
||||
didChange = NO;
|
||||
|
||||
if ([selection intersectsIndexesInRange:collapseRange])
|
||||
{
|
||||
[selection removeIndexesInRange:collapseRange];
|
||||
[self _noteSelectionIsChanging];
|
||||
didChange = YES;
|
||||
// Will call _noteSelectionDidChange
|
||||
[self _setSelectedRowIndexes:selection];
|
||||
}
|
||||
|
||||
// Shift any selected rows below upwards.
|
||||
if ([selection intersectsIndexesInRange:CPMakeRange(collapseEndIndex + 1, _itemsForRows.length)])
|
||||
{
|
||||
// Notify if that wasn't already done above.
|
||||
if (!didChange)
|
||||
[self _noteSelectionIsChanging];
|
||||
didChange = YES;
|
||||
|
||||
[selection shiftIndexesStartingAtIndex:collapseEndIndex + 1 by:-collapseRange.length];
|
||||
}
|
||||
|
||||
if (didChange)
|
||||
[self _setSelectedRowIndexes:selection];
|
||||
}
|
||||
itemInfo.isExpanded = NO;
|
||||
|
||||
[self _noteItemDidCollapse:anItem];
|
||||
|
||||
[self reloadItem:anItem reloadChildren:YES];
|
||||
@@ -500,7 +556,7 @@ CPOutlineViewDropOnItemIndex = -1;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the width of an indentation level.
|
||||
Returns the width of an indentation level.
|
||||
|
||||
@return float - the width of the indentation per level.
|
||||
*/
|
||||
@@ -580,14 +636,14 @@ CPOutlineViewDropOnItemIndex = -1;
|
||||
/*!
|
||||
Returns the frame of the disclosure button for the outline column.
|
||||
If the item is not expandable a CGZeroRect is returned.
|
||||
Subclasses can return a CGZeroRect to prevent the disclosure control from being displayed.
|
||||
Subclasses can return a CGZeroRect to prevent the disclosure control from being displayed.
|
||||
|
||||
@param aRow - The row of the reciever
|
||||
@return CGRect - The rect of the disclosure button at aRow.
|
||||
*/
|
||||
- (CGRect)frameOfOutlineDisclosureControlAtRow:(CPInteger)aRow
|
||||
{
|
||||
if (![self isExpandable:[self itemAtRow:aRow]])
|
||||
if (![self isExpandable:[self itemAtRow:aRow]])
|
||||
return _CGRectMakeZero();
|
||||
|
||||
var dataViewFrame = [self _frameOfOutlineDataViewAtRow:aRow],
|
||||
@@ -670,7 +726,7 @@ CPOutlineViewDropOnItemIndex = -1;
|
||||
Implement this to indicate whether a given item should be rendered using the group item style.
|
||||
Return YES if the item is a group item, otherwise NO.
|
||||
|
||||
@param aDelegate - the delegate object you wish to set for the reciever.
|
||||
@param aDelegate - the delegate object you wish to set for the reciever.
|
||||
*/
|
||||
- (void)setDelegate:(id)aDelegate
|
||||
{
|
||||
@@ -944,7 +1000,7 @@ CPOutlineViewDropOnItemIndex = -1;
|
||||
Retargets the drop item for the outlineview.
|
||||
To specify a drop on theItem, you specify item as theItem and index as CPOutlineViewDropOnItemIndex.
|
||||
To specify a drop between child 1 and 2 of theItem, you specify item as theItem and index as 2.
|
||||
To specify a drop on an item that canÕt be expanded theItem, you specify item as someOutlineItem and index as CPOutlineViewDropOnItemIndex.
|
||||
To specify a drop on an item that can't be expanded theItem, you specify item as someOutlineItem and index as CPOutlineViewDropOnItemIndex.
|
||||
|
||||
@param theItem - The item you want to retarget the drop on.
|
||||
@param theIndex - The index of the child item you want to retarget the drop between. Pass CPOutlineViewDropOnItemIndex if you want to drop on theItem.
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
@import <AppKit/AppKit.j>
|
||||
|
||||
@implementation CPOutlineViewTest : OJTestCase
|
||||
{
|
||||
CPOutlineView outlineView;
|
||||
CPTableColumn tableColumn;
|
||||
TestDataSource dataSource;
|
||||
}
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
outlineView = [[CPOutlineView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
|
||||
|
||||
tableColumn = [[CPTableColumn alloc] initWithIdentifier:@"Foo"];
|
||||
[outlineView addTableColumn:tableColumn];
|
||||
[outlineView setOutlineTableColumn:tableColumn];
|
||||
|
||||
[outlineView setAllowsMultipleSelection:YES];
|
||||
|
||||
dataSource = [TestDataSource new];
|
||||
[dataSource setEntries:[".1", ".1.1", ".1.2", ".1.2.1", ".1.2.2", ".2", ".3", ".3.1"]];
|
||||
|
||||
[outlineView setDataSource:dataSource];
|
||||
[outlineView expandItem:nil expandChildren:YES];
|
||||
}
|
||||
|
||||
/*!
|
||||
Test that entries load and hide correctly.
|
||||
*/
|
||||
- (void)testCollapse
|
||||
{
|
||||
// By default all rows should be visible.
|
||||
var entries = [dataSource entries];
|
||||
for (var i = 0; i < entries.length; i++)
|
||||
{
|
||||
var item = [outlineView itemAtRow:i];
|
||||
[self assert:entries[i] equals:item message:"item " + i + " visible, in correct order"];
|
||||
}
|
||||
|
||||
// Now collapse the .1 group.
|
||||
[outlineView collapseItem:".1"];
|
||||
var expected = [".1", ".2", ".3", ".3.1"];
|
||||
for (var i = 0; i < expected.length; i++)
|
||||
{
|
||||
var item = [outlineView itemAtRow:i];
|
||||
[self assert:expected[i] equals:item message:"item " + i + " visible after collapse, in correct order"];
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
Test that when an ancestor item containing a selected item is collapsed, the item is deselected.
|
||||
*/
|
||||
- (void)testCollapseDeselect
|
||||
{
|
||||
var preSelection = [CPIndexSet indexSet];
|
||||
[preSelection addIndex:[outlineView rowForItem:".1.2.2"]];
|
||||
[preSelection addIndex:[outlineView rowForItem:".1.1"]];
|
||||
|
||||
[outlineView selectRowIndexes:preSelection byExtendingSelection:NO];
|
||||
|
||||
[outlineView collapseItem:".1.2"];
|
||||
|
||||
var afterSelection = [outlineView selectedRowIndexes];
|
||||
|
||||
[self assert:1 equals:[afterSelection count] message:"1 selection should remain"];
|
||||
[self assert:".1.1" equals:[outlineView itemAtRow:[outlineView selectedRow]] message:".1.1 selection should remain"];
|
||||
}
|
||||
|
||||
/*!
|
||||
Test that the selection stays on the same item below a collapse.
|
||||
*/
|
||||
- (void)testCollapseWithSelectionBelow
|
||||
{
|
||||
// [".1", ".1.1", ".1.2", ".1.2.1", ".1.2.2", ".2", ".3", ".3.1"]
|
||||
var preSelection = [CPIndexSet indexSet];
|
||||
[preSelection addIndex:[outlineView rowForItem:".1.1"]];
|
||||
[preSelection addIndex:[outlineView rowForItem:".3.1"]];
|
||||
|
||||
[outlineView selectRowIndexes:preSelection byExtendingSelection:NO];
|
||||
|
||||
[outlineView collapseItem:".1.2"];
|
||||
|
||||
var afterSelection = [outlineView selectedRowIndexes];
|
||||
|
||||
[self assert:2 equals:[afterSelection count] message:"selections should remain"];
|
||||
[self assert:".1.1" equals:[outlineView itemAtRow:[afterSelection firstIndex]] message:".1.1 selection should remain"];
|
||||
[self assert:".3.1" equals:[outlineView itemAtRow:[afterSelection lastIndex]] message:".3.1 selection should remain"];
|
||||
|
||||
// Collapse where one selection disappears and one shifts.
|
||||
preSelection = [CPIndexSet indexSet];
|
||||
[preSelection addIndex:[outlineView rowForItem:".1.1"]];
|
||||
[preSelection addIndex:[outlineView rowForItem:".3"]];
|
||||
|
||||
[outlineView selectRowIndexes:preSelection byExtendingSelection:NO];
|
||||
|
||||
[outlineView collapseItem:".1"];
|
||||
|
||||
afterSelection = [outlineView selectedRowIndexes];
|
||||
|
||||
[self assert:1 equals:[afterSelection count] message:"1 selection should disappear"];
|
||||
|
||||
[self assert:".3" equals:[outlineView itemAtRow:[afterSelection firstIndex]] message:".3 selection should remain"];
|
||||
}
|
||||
|
||||
/*!
|
||||
Test that the selection stays on the same item below an expand.
|
||||
*/
|
||||
- (void)testExpandWithSelectionBelow
|
||||
{
|
||||
// [".1", ".1.1", ".1.2", ".1.2.1", ".1.2.2", ".2", ".3", ".3.1"]
|
||||
[outlineView collapseItem:".1.2"];
|
||||
|
||||
var preSelection = [CPIndexSet indexSet];
|
||||
[preSelection addIndex:[outlineView rowForItem:".1.1"]];
|
||||
[preSelection addIndex:[outlineView rowForItem:".2"]];
|
||||
|
||||
[outlineView selectRowIndexes:preSelection byExtendingSelection:NO];
|
||||
|
||||
[outlineView expandItem:".1.2"];
|
||||
afterSelection = [outlineView selectedRowIndexes];
|
||||
|
||||
[self assert:2 equals:[afterSelection count] message:"selections should remain"];
|
||||
|
||||
[self assert:".1.1" equals:[outlineView itemAtRow:[afterSelection firstIndex]] message:".1.1 selection should remain"];
|
||||
[self assert:".2" equals:[outlineView itemAtRow:[afterSelection lastIndex]] message:".2 selection should remain"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation TestDataSource : CPObject
|
||||
{
|
||||
CPArray entries @accessors;
|
||||
}
|
||||
|
||||
- (CPArray)childrenOfPrefix:(CPString)theItem
|
||||
{
|
||||
if (!theItem)
|
||||
theItem = "";
|
||||
|
||||
var matcher = new RegExp("^" + theItem + "\\.\\d$"),
|
||||
children = [];
|
||||
for (var i = 0; i < entries.length; i++)
|
||||
if (matcher.exec(entries[i]))
|
||||
children.push(entries[i]);
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
- (id)outlineView:(CPOutlineView)theOutlineView child:(int)theIndex ofItem:(id)theItem
|
||||
{
|
||||
return [self childrenOfPrefix:theItem][theIndex];
|
||||
}
|
||||
|
||||
- (BOOL)outlineView:(CPOutlineView)theOutlineView isItemExpandable:(id)theItem
|
||||
{
|
||||
return !![[self childrenOfPrefix:theItem] count];
|
||||
}
|
||||
|
||||
- (int)outlineView:(CPOutlineView)theOutlineView numberOfChildrenOfItem:(id)theItem
|
||||
{
|
||||
return [[self childrenOfPrefix:theItem] count];
|
||||
}
|
||||
|
||||
- (id)outlineView:(CPOutlineView)anOutlineView objectValueForTableColumn:(CPTableColumn)theColumn byItem:(id)theItem
|
||||
{
|
||||
return theItem;
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user