Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

5683 lines (4538 sloc) 187.736 kb
/*
* CPTableView.j
* AppKit
*
* Created by Francisco Tolmasky.
* Copyright 2009, 280 North, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
@import <Foundation/CPArray.j>
@import <Foundation/CPIndexSet.j>
@import "CPDragServer_Constants.j"
@import "CGGradient.j"
@import "CPCib.j"
@import "CPCompatibility.j"
@import "CPControl.j"
@import "CPImageView.j"
@import "CPScroller.j"
@import "CPScrollView.j"
@import "CPTableColumn.j"
@import "CPTableHeaderView.j"
@import "CPText.j"
@import "_CPCornerView.j"
@class CPButton
@class CPClipView
@class CPUserDefaults
@class CPTableHeaderView
@class CPClipView
@class CPButton
@global CPApp
CPTableViewColumnDidMoveNotification = @"CPTableViewColumnDidMoveNotification";
CPTableViewColumnDidResizeNotification = @"CPTableViewColumnDidResizeNotification";
CPTableViewSelectionDidChangeNotification = @"CPTableViewSelectionDidChangeNotification";
CPTableViewSelectionIsChangingNotification = @"CPTableViewSelectionIsChangingNotification";
var CPTableViewDataSource_numberOfRowsInTableView_ = 1 << 0,
CPTableViewDataSource_tableView_objectValueForTableColumn_row_ = 1 << 1,
CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_ = 1 << 2,
CPTableViewDataSource_tableView_acceptDrop_row_dropOperation_ = 1 << 3,
CPTableViewDataSource_tableView_namesOfPromisedFilesDroppedAtDestination_forDraggedRowsWithIndexes_ = 1 << 4,
CPTableViewDataSource_tableView_validateDrop_proposedRow_proposedDropOperation_ = 1 << 5,
CPTableViewDataSource_tableView_writeRowsWithIndexes_toPasteboard_ = 1 << 6,
CPTableViewDataSource_tableView_sortDescriptorsDidChange_ = 1 << 7;
var CPTableViewDelegate_selectionShouldChangeInTableView_ = 1 << 0,
CPTableViewDelegate_tableView_viewForTableColumn_row_ = 1 << 1,
CPTableViewDelegate_tableView_dataViewForTableColumn_row_ = 1 << 21,
CPTableViewDelegate_tableView_didClickTableColumn_ = 1 << 2,
CPTableViewDelegate_tableView_didDragTableColumn_ = 1 << 3,
CPTableViewDelegate_tableView_heightOfRow_ = 1 << 4,
CPTableViewDelegate_tableView_isGroupRow_ = 1 << 5,
CPTableViewDelegate_tableView_mouseDownInHeaderOfTableColumn_ = 1 << 6,
CPTableViewDelegate_tableView_nextTypeSelectMatchFromRow_toRow_forString_ = 1 << 7,
CPTableViewDelegate_tableView_selectionIndexesForProposedSelection_ = 1 << 8,
CPTableViewDelegate_tableView_shouldEditTableColumn_row_ = 1 << 9,
CPTableViewDelegate_tableView_shouldSelectRow_ = 1 << 10,
CPTableViewDelegate_tableView_shouldSelectTableColumn_ = 1 << 11,
CPTableViewDelegate_tableView_shouldShowViewExpansionForTableColumn_row_ = 1 << 12,
CPTableViewDelegate_tableView_shouldTrackView_forTableColumn_row_ = 1 << 13,
CPTableViewDelegate_tableView_shouldTypeSelectForEvent_withCurrentSearchString_ = 1 << 14,
CPTableViewDelegate_tableView_toolTipForView_rect_tableColumn_row_mouseLocation_ = 1 << 15,
CPTableViewDelegate_tableView_typeSelectStringForTableColumn_row_ = 1 << 16,
CPTableViewDelegate_tableView_willDisplayView_forTableColumn_row_ = 1 << 17,
CPTableViewDelegate_tableViewSelectionDidChange_ = 1 << 18,
CPTableViewDelegate_tableViewSelectionIsChanging_ = 1 << 19,
CPTableViewDelegate_tableViewMenuForTableColumn_Row_ = 1 << 20;
//CPTableViewDraggingDestinationFeedbackStyles
CPTableViewDraggingDestinationFeedbackStyleNone = -1;
CPTableViewDraggingDestinationFeedbackStyleRegular = 0;
CPTableViewDraggingDestinationFeedbackStyleSourceList = 1;
//CPTableViewDropOperations
CPTableViewDropOn = 0;
CPTableViewDropAbove = 1;
CPSourceListGradient = @"CPSourceListGradient";
CPSourceListTopLineColor = @"CPSourceListTopLineColor";
CPSourceListBottomLineColor = @"CPSourceListBottomLineColor";
// TODO: add docs
CPTableViewSelectionHighlightStyleNone = -1;
CPTableViewSelectionHighlightStyleRegular = 0;
CPTableViewSelectionHighlightStyleSourceList = 1;
CPTableViewGridNone = 0;
CPTableViewSolidVerticalGridLineMask = 1 << 0;
CPTableViewSolidHorizontalGridLineMask = 1 << 1;
CPTableViewNoColumnAutoresizing = 0;
CPTableViewUniformColumnAutoresizingStyle = 1; // FIX ME: This is FUBAR
CPTableViewSequentialColumnAutoresizingStyle = 2;
CPTableViewReverseSequentialColumnAutoresizingStyle = 3;
CPTableViewLastColumnOnlyAutoresizingStyle = 4;
CPTableViewFirstColumnOnlyAutoresizingStyle = 5;
#define NUMBER_OF_COLUMNS() (_tableColumns.length)
#define UPDATE_COLUMN_RANGES_IF_NECESSARY() if (_dirtyTableColumnRangeIndex !== CPNotFound) [self _recalculateTableColumnRanges];
@implementation _CPTableDrawView : CPView
{
CPTableView _tableView;
}
- (id)initWithTableView:(CPTableView)aTableView
{
self = [super init];
if (self)
_tableView = aTableView;
return self;
}
- (void)drawRect:(CGRect)aRect
{
var frame = [self frame],
context = [[CPGraphicsContext currentContext] graphicsPort];
CGContextTranslateCTM(context, -CGRectGetMinX(frame), -CGRectGetMinY(frame));
[_tableView _drawRect:aRect];
}
@end
/*!
@ingroup appkit
@class CPTableView
CPTableView object displays record-oriented data in a table and
allows the user to edit values and resize and rearrange columns.
A CPTableView requires you to either set a data source which implements
@ref numberofrows "numberOfRowsInTableView:" and @ref objectValueForTable "tableView:objectValueForTableColumn:row:",
or alternatively to provide data through Key Value Bindings.
To use a table view with Key Value Bindings, bind each column's
\c CPValueBinding to an \c array.field path - typically this would be to a path
in an array controller like \c arrangedObjects.X, where \c X is the name of a
field. Optionally also bind the table's \c selectionIndexes and
\c sortDescriptors to the array controller.
All delegate and data source methods are documented in the @ref setdatasource "setDataSource:" and @ref setdelegate "setDelegate:" methods.
If you want to display something other than just text in the table you should call @link CPTableColumn::setDataView: setDataView:@endlink on a CPTableColumn object. More documentation in that class including theme states.
@note CPTableView does not contain its own scrollview. You should be sure you place the tableview in a CPScrollView on your own.
*/
@implementation CPTableView : CPControl
{
id _dataSource;
CPInteger _implementedDataSourceMethods;
id _delegate;
CPInteger _implementedDelegateMethods;
CPArray _tableColumns;
CPArray _tableColumnRanges;
CPInteger _dirtyTableColumnRangeIndex;
CPInteger _numberOfHiddenColumns;
BOOL _reloadAllRows;
Object _objectValues;
CGRect _exposedRect;
CPIndexSet _exposedRows;
CPIndexSet _exposedColumns;
Object _dataViewsForTableColumns;
Object _cachedDataViews;
CPDictionary _archivedDataViews;
Object _unavailable_custom_cibs;
//Configuring Behavior
BOOL _allowsColumnReordering;
BOOL _allowsColumnResizing;
BOOL _allowsColumnSelection;
BOOL _allowsMultipleSelection;
BOOL _allowsEmptySelection;
CPArray _sortDescriptors;
//Setting Display Attributes
CGSize _intercellSpacing;
float _rowHeight;
BOOL _usesAlternatingRowBackgroundColors;
CPArray _alternatingRowBackgroundColors;
unsigned _selectionHighlightStyle;
CPColor _unfocusedSelectionHighlightColor;
CPDictionary _unfocusedSourceListSelectionColor;
CPTableColumn _currentHighlightedTableColumn;
unsigned _gridStyleMask;
unsigned _numberOfRows;
CPIndexSet _groupRows;
CPArray _cachedRowHeights;
// Persistence
CPString _autosaveName;
BOOL _autosaveTableColumns;
CPTableHeaderView _headerView;
_CPCornerView _cornerView;
CPIndexSet _selectedColumnIndexes;
CPIndexSet _selectedRowIndexes;
CPInteger _selectionAnchorRow;
CPInteger _lastSelectedRow;
CPIndexSet _previouslySelectedRowIndexes;
CGPoint _startTrackingPoint;
CPDate _startTrackingTimestamp;
BOOL _trackingPointMovedOutOfClickSlop;
CGPoint _editingCellIndex;
CPInteger _editingRow;
CPInteger _editingColumn;
_CPTableDrawView _tableDrawView;
SEL _doubleAction;
CPInteger _clickedRow;
CPInteger _clickedColumn;
unsigned _columnAutoResizingStyle;
int _lastTrackedRowIndex;
CGPoint _originalMouseDownPoint;
BOOL _verticalMotionCanDrag;
unsigned _destinationDragStyle;
BOOL _isSelectingSession;
CPIndexSet _draggedRowIndexes;
BOOL _wasSelectionBroken;
_CPDropOperationDrawingView _dropOperationFeedbackView;
CPDragOperation _dragOperationDefaultMask;
int _retargetedDropRow;
CPDragOperation _retargetedDropOperation;
CPArray _draggingViews;
BOOL _disableAutomaticResizing @accessors(property=disableAutomaticResizing);
BOOL _lastColumnShouldSnap;
BOOL _implementsCustomDrawRow;
BOOL _isViewBased;
BOOL _contentBindingExpicitelySet;
SEL _viewForTableColumnRowSelector;
CPTableColumn _draggedColumn;
CPArray _differedColumnDataToRemove;
}
/*!
@ignore
*/
+ (CPString)defaultThemeClass
{
return @"tableview";
}
/*!
@ignore
*/
+ (id)themeAttributes
{
return @{
@"alternating-row-colors": [CPNull null],
@"grid-color": [CPNull null],
@"highlighted-grid-color": [CPNull null],
@"selection-color": [CPNull null],
@"sourcelist-selection-color": [CPNull null],
@"sort-image": [CPNull null],
@"sort-image-reversed": [CPNull null],
@"selection-radius": [CPNull null],
@"image-generic-file": [CPNull null],
@"default-row-height": 25.0,
};
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
if (self)
{
//Configuring Behavior
_allowsColumnReordering = YES;
_allowsColumnResizing = YES;
_allowsMultipleSelection = NO;
_allowsEmptySelection = YES;
_allowsColumnSelection = NO;
_disableAutomaticResizing = NO;
//Setting Display Attributes
_selectionHighlightStyle = CPTableViewSelectionHighlightStyleRegular;
[self setUsesAlternatingRowBackgroundColors:NO];
[self setAlternatingRowBackgroundColors:
[[CPColor whiteColor], [CPColor colorWithRed:245.0 / 255.0 green:249.0 / 255.0 blue:252.0 / 255.0 alpha:1.0]]];
_tableColumns = [];
_tableColumnRanges = [];
_dirtyTableColumnRangeIndex = CPNotFound;
_numberOfHiddenColumns = 0;
_intercellSpacing = CGSizeMake(3.0, 2.0);
_rowHeight = [self valueForThemeAttribute:@"default-row-height"];
[self setGridColor:[CPColor colorWithHexString:@"dce0e2"]];
[self setGridStyleMask:CPTableViewGridNone];
[self setHeaderView:[[CPTableHeaderView alloc] initWithFrame:CGRectMake(0, 0, [self bounds].size.width, _rowHeight)]];
[self setCornerView:[[_CPCornerView alloc] initWithFrame:CGRectMake(0, 0, [CPScroller scrollerWidth], CGRectGetHeight([_headerView frame]))]];
_currentHighlightedTableColumn = nil;
_draggedRowIndexes = [CPIndexSet indexSet];
_verticalMotionCanDrag = YES;
_isSelectingSession = NO;
_retargetedDropRow = nil;
_retargetedDropOperation = nil;
_dragOperationDefaultMask = nil;
_destinationDragStyle = CPTableViewDraggingDestinationFeedbackStyleRegular;
_contentBindingExpicitelySet = NO;
[self setBackgroundColor:[CPColor whiteColor]];
[self _init];
}
return self;
}
/*!
@ignore
FIX ME: we have a lot of redundant init stuff in initWithFrame: and initWithCoder: we should move it all into here.
we should do a full audit of all the initializers before 1.0
*/
- (void)_init
{
_lastSelectedRow = _clickedColumn = _clickedRow = -1;
_selectedColumnIndexes = [CPIndexSet indexSet];
_selectedRowIndexes = [CPIndexSet indexSet];
_dropOperationFeedbackView = [[_CPDropOperationDrawingView alloc] initWithFrame:CGRectMakeZero()];
[_dropOperationFeedbackView setTableView:self];
_lastColumnShouldSnap = NO;
if (!_alternatingRowBackgroundColors)
_alternatingRowBackgroundColors = [[CPColor whiteColor], [CPColor colorWithHexString:@"e4e7ff"]];
_tableColumnRanges = [];
_dirtyTableColumnRangeIndex = 0;
_numberOfHiddenColumns = 0;
_objectValues = { };
_dataViewsForTableColumns = { };
_numberOfRows = 0;
_exposedRows = [CPIndexSet indexSet];
_exposedColumns = [CPIndexSet indexSet];
_cachedDataViews = { };
_archivedDataViews = nil;
_viewForTableColumnRowSelector = nil;
_unavailable_custom_cibs = { };
_cachedRowHeights = [];
_groupRows = [CPIndexSet indexSet];
_tableDrawView = [[_CPTableDrawView alloc] initWithTableView:self];
[_tableDrawView setBackgroundColor:[CPColor clearColor]];
[self addSubview:_tableDrawView];
_draggedColumn = nil;
_draggingViews = [CPArray array];
_editingRow = CPNotFound;
_editingColumn = CPNotFound;
/* //gradients for the source list when CPTableView is NOT first responder or the window is NOT key
// FIX ME: we need to actually implement this.
_sourceListInactiveGradient = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(), [168.0/255.0,183.0/255.0,205.0/255.0,1.0,157.0/255.0,174.0/255.0,199.0/255.0,1.0], [0,1], 2);
_sourceListInactiveTopLineColor = [CPColor colorWithCalibratedRed:(173.0/255.0) green:(187.0/255.0) blue:(209.0/255.0) alpha:1.0];
_sourceListInactiveBottomLineColor = [CPColor colorWithCalibratedRed:(150.0/255.0) green:(161.0/255.0) blue:(183.0/255.0) alpha:1.0];*/
_differedColumnDataToRemove = [];
_implementsCustomDrawRow = [self implementsSelector:@selector(drawRow:clipRect:)];
if (!_sortDescriptors)
_sortDescriptors = [];
[self _startObservingFirstResponder];
}
/*!
@anchor setdatasource
Sets the receiver's data source to a given object.
The data source implements various methods for handling the tableview's data when bindings are not used.
@param anObject The data source for the receiver. This object must implement numberOfRowsInTableView: and tableView:objectValueForTableColumn:row:
These methods are outlined below.
@section overview Overview
CPTableView generally requires a datasource to run. This data source can be thought of just like a delegate, but specifically for the tableview data.
Methods include:
@section required Required Methods
@anchor numberofrows
Returns the number of rows in the tableview
@code
- (int)numberOfRowsInTableView:(CPTableView)aTableView;
@endcode
Returns the object value for each dataview. Each dataview will be sent a setObjectValue: method which will contain the object you return from this datasource method.
@anchor objectValueForTable
@code
- (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aColumn row:(int)aRowIndex;
@endcode
@section editing Editing:
Sets the data object for an item in a given row and column. This needs to be implemented if you want inline editing support
@code
- (void)tableView:(CPTableView)aTableView setObjectValue:(id)anObject forTableColumn:(CPTableColumn)aTableColumn row:(int)rowIndex;
@endcode
@section sorting Sorting:
The tableview will call this method if you click the tableheader. You should sort the datasource based off of the new sort descriptors and reload the data
@code
- (void)tableView:(CPTableView)aTableView sortDescriptorsDidChange:(CPArray)oldDescriptors;
@endcode
@section draganddrop Drag and Drop:
@note In order for the tableview to receive drops don't forget to first register the tableview for drag types like you do with every other view.
Return the drag operation (move, copy, etc) that should be performed if a registered drag type is over the tableview
The data source can retarget a drop if you want by calling <pre>-(void)setDropRow:(int)aRow dropOperation:(CPTableViewDropOperation)anOperation;</pre>
@code
- (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(CPDraggingInfo)info proposedRow:(int)row proposedDropOperation:(CPTableViewDropOperation)operation;
@endcode
Returns YES if the drop operation is allowed otherwise NO. This method is invoked by the tableview after a drag should begin, but before it is started. If you don't 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.
@code
- (BOOL)tableView:(CPTableView)aTableView writeRowsWithIndexes:(CPIndexSet)rowIndexes toPasteboard:(CPPasteboard)pboard;
@endcode
Return YES if the operation was successful otherwise return NO. The data source should incorporate the data from the dragging pasteboard in this method implementation. To get this data use the draggingPasteboard method on the CPDraggingInfo object.
@code
- (BOOL)tableView:(CPTableView)aTableView acceptDrop:(CPDraggingInfo)info row:(int)row dropOperation:(CPTableViewDropOperation)operation;
@endcode
NOT YET IMPLEMENTED
@code
- (CPArray)tableView:(CPTableView)aTableView namesOfPromisedFilesDroppedAtDestination:(CPURL)dropDestination forDraggedRowsWithIndexes:(CPIndexSet)indexSet;
@endcode
*/
- (void)setDataSource:(id)aDataSource
{
if (_dataSource === aDataSource)
return;
_dataSource = aDataSource;
_implementedDataSourceMethods = 0;
if (!_dataSource)
return;
var hasContentBinding = !![self infoForBinding:@"content"];
if ([_dataSource respondsToSelector:@selector(numberOfRowsInTableView:)])
_implementedDataSourceMethods |= CPTableViewDataSource_numberOfRowsInTableView_;
if ([_dataSource respondsToSelector:@selector(tableView:objectValueForTableColumn:row:)])
_implementedDataSourceMethods |= CPTableViewDataSource_tableView_objectValueForTableColumn_row_;
if ([_dataSource respondsToSelector:@selector(tableView:setObjectValue:forTableColumn:row:)])
_implementedDataSourceMethods |= CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_;
if ([_dataSource respondsToSelector:@selector(tableView:acceptDrop:row:dropOperation:)])
_implementedDataSourceMethods |= CPTableViewDataSource_tableView_acceptDrop_row_dropOperation_;
if ([_dataSource respondsToSelector:@selector(tableView:namesOfPromisedFilesDroppedAtDestination:forDraggedRowsWithIndexes:)])
_implementedDataSourceMethods |= CPTableViewDataSource_tableView_namesOfPromisedFilesDroppedAtDestination_forDraggedRowsWithIndexes_;
if ([_dataSource respondsToSelector:@selector(tableView:validateDrop:proposedRow:proposedDropOperation:)])
_implementedDataSourceMethods |= CPTableViewDataSource_tableView_validateDrop_proposedRow_proposedDropOperation_;
if ([_dataSource respondsToSelector:@selector(tableView:writeRowsWithIndexes:toPasteboard:)])
_implementedDataSourceMethods |= CPTableViewDataSource_tableView_writeRowsWithIndexes_toPasteboard_;
if ([_dataSource respondsToSelector:@selector(tableView:sortDescriptorsDidChange:)])
_implementedDataSourceMethods |= CPTableViewDataSource_tableView_sortDescriptorsDidChange_;
[self reloadData];
}
/*!
Returns the object that provides the data displayed by the receiver.
*/
- (id)dataSource
{
return _dataSource;
}
//Loading Data
/*!
Reloads the data for only the specified rows and columns.
@param rowIndexes The indexes of the rows to update.
@param columnIndexes The indexes of the columns to update.
*/
- (void)reloadDataForRowIndexes:(CPIndexSet)rowIndexes columnIndexes:(CPIndexSet)columnIndexes
{
[self reloadData];
// [_previouslyExposedRows removeIndexes:rowIndexes];
// [_previouslyExposedColumns removeIndexes:columnIndexes];
}
/*!
Reloads the data for all rows and columns.
*/
- (void)reloadData
{
//if (!_dataSource)
// return;
_reloadAllRows = YES;
_objectValues = { };
_cachedRowHeights = [];
// Otherwise, if we have a row marked as group with a
// index greater than the new number or rows
// it keeps the the graphical group style.
[_groupRows removeAllIndexes];
// This updates the size too.
[self noteNumberOfRowsChanged];
[self setNeedsLayout];
[self setNeedsDisplay:YES];
}
//Target-action Behavior
/*!
Sets the message sent to the target when the user double-clicks an
uneditable cell or a column header to a given selector.
@param aSelector The message the receiver sends to its target when the user
double-clicks an uneditable cell or a column header.
*/
- (void)setDoubleAction:(SEL)anAction
{
_doubleAction = anAction;
}
/*!
Returns the double click action selector.
*/
- (SEL)doubleAction
{
return _doubleAction;
}
/*
Returns the index of the the column the user clicked to trigger an action, or -1 if no column was clicked.
*/
- (CPInteger)clickedColumn
{
return _clickedColumn;
}
/*!
Returns the index of the the row the user clicked to trigger an action, or -1 if no row was clicked.
*/
- (CPInteger)clickedRow
{
return _clickedRow;
}
//Configuring Behavior
/*!
If you want to allow the user to reorder the columns pass YES, otherwise NO.
*/
- (void)setAllowsColumnReordering:(BOOL)shouldAllowColumnReordering
{
_allowsColumnReordering = !!shouldAllowColumnReordering;
}
/*!
Returns YES if the user is allowed to reorder the columns, otherwise NO.
*/
- (BOOL)allowsColumnReordering
{
return _allowsColumnReordering;
}
/*!
Passing YES will allow the user to resize columns. Passing NO will keep the table columns unmovable by the user.
@note The this does not affect autoresizing behavior.
*/
- (void)setAllowsColumnResizing:(BOOL)shouldAllowColumnResizing
{
_allowsColumnResizing = !!shouldAllowColumnResizing;
}
/*!
Returns YES if the user is allowed to manually resize the columns, otherwise NO.
*/
- (BOOL)allowsColumnResizing
{
return _allowsColumnResizing;
}
/*!
Controls whether the user can select more than one row or column at a time.
@param aFlag YES to allow the user to select multiple rows or columns, otherwise NO.
*/
- (void)setAllowsMultipleSelection:(BOOL)shouldAllowMultipleSelection
{
_allowsMultipleSelection = !!shouldAllowMultipleSelection;
}
/*!
Returns YES if the tableview is allowed to have multiple selections, otherwise NO.
@return BOOL - YES if the tableview is allowed to have multiple selections otherwise NO.
*/
- (BOOL)allowsMultipleSelection
{
return _allowsMultipleSelection;
}
/*!
Controls whether the receiver allows zero rows or columns to be selected.
@param aFlag YES if an empty selection is allowed, otherwise NO.
*/
- (void)setAllowsEmptySelection:(BOOL)shouldAllowEmptySelection
{
_allowsEmptySelection = !!shouldAllowEmptySelection;
}
/*!
Returns YES if the tableview is allowed to have an unselected row or column, otherwise NO.
*/
- (BOOL)allowsEmptySelection
{
return _allowsEmptySelection;
}
/*!
Controls whether the user can select an entire column by clicking its header.
@param aFlag YES to allow the user to select columns, otherwise NO.
*/
- (void)setAllowsColumnSelection:(BOOL)shouldAllowColumnSelection
{
_allowsColumnSelection = !!shouldAllowColumnSelection;
}
/*!
Returns YES if the user is allowed to select a column by clicking it, otherwise NO.
*/
- (BOOL)allowsColumnSelection
{
return _allowsColumnSelection;
}
//Setting Display Attributes
/*!
Sets the width and height between dataviews.
This value is (3.0, 2.0) by default.
@param aSize a CGSize object that defines the space between the cells
*/
- (void)setIntercellSpacing:(CGSize)aSize
{
if (CGSizeEqualToSize(_intercellSpacing, aSize))
return;
_intercellSpacing = CGSizeMakeCopy(aSize);
_dirtyTableColumnRangeIndex = 0; // so that _recalculateTableColumnRanges will work
[self _recalculateTableColumnRanges];
[self setNeedsLayout];
[_headerView setNeedsDisplay:YES];
[_headerView setNeedsLayout];
[self reloadData];
}
/*!
Returns the intercell spacing in a CGSize object.
*/
- (CGSize)intercellSpacing
{
return CGSizeMakeCopy(_intercellSpacing);
}
/*!
Sets the height of each row.
@note This may still used even if variable row height is being used.
@param aRowHeight the height of each row
*/
- (void)setRowHeight:(unsigned)aRowHeight
{
// Accept row heights such as "0".
aRowHeight = +aRowHeight;
if (_rowHeight === aRowHeight)
return;
_rowHeight = MAX(0.0, aRowHeight);
[self setNeedsLayout];
}
/*!
Returns the height of each row.
*/
- (unsigned)rowHeight
{
return _rowHeight;
}
/*!
Sets whether the receiver uses the standard alternating row colors for its background.
@param aFlag YES to specify standard alternating row colors for the background, NO to specify a solid color.
*/
- (void)setUsesAlternatingRowBackgroundColors:(BOOL)shouldUseAlternatingRowBackgroundColors
{
_usesAlternatingRowBackgroundColors = shouldUseAlternatingRowBackgroundColors;
}
/*!
Returns YES if the tableview uses alternating row background colors, otherwise NO.
*/
- (BOOL)usesAlternatingRowBackgroundColors
{
return _usesAlternatingRowBackgroundColors;
}
/*!
Sets the colors for the rows as they alternate. The number of colors can be arbitrary. By default these colors are white and light blue.
@param anArray an array of CPColors
*/
- (void)setAlternatingRowBackgroundColors:(CPArray)alternatingRowBackgroundColors
{
[self setValue:alternatingRowBackgroundColors forThemeAttribute:@"alternating-row-colors"];
[self setNeedsDisplay:YES];
}
/*!
Returns an array of the alternating background colors
*/
- (CPArray)alternatingRowBackgroundColors
{
return [self currentValueForThemeAttribute:@"alternating-row-colors"];
}
/*!
Returns an enumerated value for the selection highlight style.
Valid values are:
<pre>
CPTableViewSelectionHighlightStyleNone
CPTableViewSelectionHighlightStyleRegular
CPTableViewSelectionHighlightStyleSourceList
</pre>
*/
- (unsigned)selectionHighlightStyle
{
return _selectionHighlightStyle;
}
/*!
Sets the selection highlight style to an enumerated value.
This value can also affect the way the tableview draws feedback when the user is dragging.
Valid values are:
<pre>
CPTableViewSelectionHighlightStyleNone
CPTableViewSelectionHighlightStyleRegular
CPTableViewSelectionHighlightStyleSourceList
</pre>
*/
- (void)setSelectionHighlightStyle:(unsigned)aSelectionHighlightStyle
{
_selectionHighlightStyle = aSelectionHighlightStyle;
if (aSelectionHighlightStyle === CPTableViewSelectionHighlightStyleSourceList)
_destinationDragStyle = CPTableViewDraggingDestinationFeedbackStyleSourceList;
else
_destinationDragStyle = CPTableViewDraggingDestinationFeedbackStyleRegular;
[self _updateHighlightWithOldRows:[CPIndexSet indexSet] newRows:_selectedRowIndexes];
[self _updateHighlightWithOldColumns:[CPIndexSet indexSet] newColumns:_selectedColumnIndexes];
[self setNeedsDisplay:YES];
}
/*!
Sets the highlight color for a row or column selection.
@param aColor a CPColor
*/
- (void)setSelectionHighlightColor:(CPColor)aColor
{
if ([[self selectionHighlightColor] isEqual:aColor])
return;
[self setValue:aColor forThemeAttribute:@"selection-color"];
[self setNeedsDisplay:YES];
}
/*!
Returns the highlight color for a focused row or column selection.
*/
- (CPColor)selectionHighlightColor
{
return [self currentValueForThemeAttribute:@"selection-color"];
}
/*!
Returns the highlight color for an unfocused row or column selection.
*/
- (CPColor)unfocusedSelectionHighlightColor
{
if (!_unfocusedSelectionHighlightColor)
_unfocusedSelectionHighlightColor = [self _unfocusedSelectionColorFromColor:[self selectionHighlightColor] saturation:0];
return _unfocusedSelectionHighlightColor;
}
/*!
Sets the highlight gradient for a row or column selection
This is specific to the
@param aDictionary a CPDictionary expects three keys to be set:
<pre>
CPSourceListGradient which is a CGGradient
CPSourceListTopLineColor which is a CPColor
CPSourceListBottomLineColor which is a CPColor
</pre>
*/
- (void)setSelectionGradientColors:(CPDictionary)aDictionary
{
[self setValue:aDictionary forThemeAttribute:@"sourcelist-selection-color"];
[self setNeedsDisplay:YES];
}
/*!
Returns a dictionary of containing the keys:
<pre>
CPSourceListGradient
CPSourceListTopLineColor
CPSourceListBottomLineColor
</pre>
*/
- (CPDictionary)selectionGradientColors
{
return [self currentValueForThemeAttribute:@"sourcelist-selection-color"];
}
/*!
Returns a dictionary of containing the keys:
<pre>
CPSourceListGradient
CPSourceListTopLineColor
CPSourceListBottomLineColor
</pre>
*/
- (void)unfocusedSelectionGradientColors
{
if (!_unfocusedSourceListSelectionColor)
{
var sourceListColors = [self selectionGradientColors];
_unfocusedSourceListSelectionColor = @{
CPSourceListGradient: [self _unfocusedGradientFromGradient:[sourceListColors objectForKey:CPSourceListGradient]],
CPSourceListTopLineColor: [self _unfocusedSelectionColorFromColor:[sourceListColors objectForKey:CPSourceListTopLineColor] saturation:0.2],
CPSourceListBottomLineColor: [self _unfocusedSelectionColorFromColor:[sourceListColors objectForKey:CPSourceListBottomLineColor] saturation:0.2]
};
}
return _unfocusedSourceListSelectionColor;
}
- (CPColor)_unfocusedSelectionColorFromColor:(CPColor)aColor saturation:(float)saturation
{
var hsb = [aColor hsbComponents];
return [CPColor colorWithHue:hsb[0] saturation:hsb[1] * saturation brightness:hsb[2]];
}
- (CGGradient)_unfocusedGradientFromGradient:(CGGradient)aGradient
{
var colors = [aGradient.colors copy],
count = [colors count];
while (count--)
{
var rgba = colors[count].components,
hsb = [self _unfocusedSelectionColorFromColor:[CPColor colorWithRed:rgba[0] green:rgba[1] blue:rgba[2] alpha:rgba[3]] saturation:0.2];
colors[count] = CGColorCreate(aGradient.colorspace, [[hsb components] copy]);
}
return CGGradientCreateWithColors(aGradient.colorspace, colors, aGradient.locations);
}
/*!
Sets the grid color in the non highlighted state.
@param aColor a CPColor
*/
- (void)setGridColor:(CPColor)aColor
{
[self setValue:aColor forThemeAttribute:@"grid-color"];
[self setNeedsDisplay:YES];
}
/*!
Returns a CPColor object of set grid color
*/
- (CPColor)gridColor
{
return [self currentValueForThemeAttribute:@"grid-color"];;
}
/*!
Sets the grid style mask to specify if no grid lines, vertical grid lines, or horizontal grid lines should be displayed.
@param gridType The grid style mask. CPTableViewGridNone, CPTableViewSolidVerticalGridLineMask, CPTableViewSolidHorizontalGridLineMask
*/
- (void)setGridStyleMask:(unsigned)aGrideStyleMask
{
if (_gridStyleMask === aGrideStyleMask)
return;
_gridStyleMask = aGrideStyleMask;
[self setNeedsDisplay:YES];
}
/*!
Returns a grid mask
*/
- (unsigned)gridStyleMask
{
return _gridStyleMask;
}
//Column Management
/*!
Adds a given column as the last column of the receiver.
@param aColumn The column to add to the receiver.
*/
- (void)addTableColumn:(CPTableColumn)aTableColumn
{
[_tableColumns addObject:aTableColumn];
[aTableColumn setTableView:self];
if (_dirtyTableColumnRangeIndex < 0)
_dirtyTableColumnRangeIndex = NUMBER_OF_COLUMNS() - 1;
else
_dirtyTableColumnRangeIndex = MIN(NUMBER_OF_COLUMNS() - 1, _dirtyTableColumnRangeIndex);
if ([[self sortDescriptors] count] > 0)
{
var mainSortDescriptor = [[self sortDescriptors] objectAtIndex:0];
if (aTableColumn === [self _tableColumnForSortDescriptor:mainSortDescriptor])
{
var image = [mainSortDescriptor ascending] ? [self _tableHeaderSortImage] : [self _tableHeaderReverseSortImage];
[self setIndicatorImage:image inTableColumn:aTableColumn];
}
}
[self tile];
[self setNeedsLayout];
}
/*!
Removes a given column from the receiver.
@param aTableColumn The column to remove from the receiver.
*/
- (void)removeTableColumn:(CPTableColumn)aTableColumn
{
if ([aTableColumn tableView] !== self)
return;
var index = [_tableColumns indexOfObjectIdenticalTo:aTableColumn];
if (index === CPNotFound)
return;
// we defer the actual removal until the end of the runloop in order to keep a reference to the column.
[_differedColumnDataToRemove addObject:{"column":aTableColumn, "shouldBeHidden": [aTableColumn isHidden]}];
[aTableColumn setHidden:YES];
[aTableColumn setTableView:nil];
var tableColumnUID = [aTableColumn UID];
if (_objectValues[tableColumnUID])
_objectValues[tableColumnUID] = nil;
if (_dirtyTableColumnRangeIndex < 0)
_dirtyTableColumnRangeIndex = index;
else
_dirtyTableColumnRangeIndex = MIN(index, _dirtyTableColumnRangeIndex);
[self setNeedsLayout];
}
/*!
@ignore
Internally used to set a column that will be dragged
*/
- (void)_setDraggedColumn:(CPTableColumn)aColumn
{
if (_draggedColumn === aColumn)
return;
var previouslyDraggedColumn = _draggedColumn;
_draggedColumn = aColumn;
// if a column is currently being dragged, update that column (removing data views)
if (aColumn)
[self reloadDataForRowIndexes:_exposedRows columnIndexes:[CPIndexSet indexSetWithIndex:[_tableColumns indexOfObject:aColumn]]];
// when the column is dropped, we should also update it.
if (previouslyDraggedColumn)
[self reloadDataForRowIndexes:_exposedRows columnIndexes:[CPIndexSet indexSetWithIndex:[_tableColumns indexOfObject:previouslyDraggedColumn]]];
}
/*
@ignore
Same as moveColumn:toColumn: but doesn't trigger an autosave
*/
- (void)_moveColumn:(unsigned)fromIndex toColumn:(unsigned)toIndex
{
// Convert parameters such as "0" to 0.
fromIndex = +fromIndex;
toIndex = +toIndex;
if (fromIndex === toIndex)
return;
if (_dirtyTableColumnRangeIndex < 0)
_dirtyTableColumnRangeIndex = MIN(fromIndex, toIndex);
else
_dirtyTableColumnRangeIndex = MIN(fromIndex, toIndex, _dirtyTableColumnRangeIndex);
var tableColumn = _tableColumns[fromIndex];
[_tableColumns removeObjectAtIndex:fromIndex];
[_tableColumns insertObject:tableColumn atIndex:toIndex];
[[self headerView] setNeedsLayout];
[[self headerView] setNeedsDisplay:YES];
var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])],
columnIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(fromIndex, toIndex)];
[self reloadDataForRowIndexes:rowIndexes columnIndexes:columnIndexes];
// Notify even if programmatically moving a column as in Cocoa.
// TODO Only notify when a column drag operation ends, not each time a column reaches a new slot?
[[CPNotificationCenter defaultCenter] postNotificationName:CPTableViewColumnDidMoveNotification
object:self
userInfo:@{ @"CPOldColumn": fromIndex, @"CPNewColumn": toIndex }];
}
/*!
Moves the column and heading at a given index to a new given index.
@param theColumnIndex The current index of the column to move.
@param theToIndex The new index for the moved column.
*/
- (void)moveColumn:(int)theColumnIndex toColumn:(int)theToIndex
{
[self _moveColumn:theColumnIndex toColumn:theToIndex];
[self _autosave];
}
/*!
@ignore
Called when a table column changes visibility
*/
- (void)_tableColumnVisibilityDidChange:(CPTableColumn)aColumn
{
var columnIndex = [[self tableColumns] indexOfObjectIdenticalTo:aColumn];
if (_dirtyTableColumnRangeIndex < 0)
_dirtyTableColumnRangeIndex = columnIndex;
else
_dirtyTableColumnRangeIndex = MIN(columnIndex, _dirtyTableColumnRangeIndex);
[[self headerView] setNeedsLayout];
[[self headerView] setNeedsDisplay:YES];
var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])];
[self reloadDataForRowIndexes:rowIndexes columnIndexes:[CPIndexSet indexSetWithIndex:columnIndex]];
}
/*!
Returns an array of CPTableColumns of all the receiver's columns.
*/
- (CPArray)tableColumns
{
return _tableColumns;
}
/*!
Returns the index of the column with the specified identifier
@param anIdentifier the string value of the tablecolumn identifier
@return the index of the column
*/
- (CPInteger)columnWithIdentifier:(CPString)anIdentifier
{
var index = 0,
count = NUMBER_OF_COLUMNS();
for (; index < count; ++index)
if ([_tableColumns[index] identifier] === anIdentifier)
return index;
return CPNotFound;
}
/*!
Returns the CPTableColumn object with the given identifier string.
@param anIdentifier the string value of the identifier
@return a CPTableColumn object with the given identifier
*/
- (CPTableColumn)tableColumnWithIdentifier:(CPString)anIdentifier
{
var index = [self columnWithIdentifier:anIdentifier];
if (index === CPNotFound)
return nil;
return _tableColumns[index];
}
/*!
@ignore
*/
- (void)_didResizeTableColumn:(CPTableColumn)theColumn
{
[self _autosave];
}
//Selecting Columns and Rows
/*!
Sets the column selection using indexes.
@param columns a CPIndexSet of columns to select
@param aFlag should extend the selection thereby retaining the previous selection
*/
- (void)selectColumnIndexes:(CPIndexSet)columns byExtendingSelection:(BOOL)shouldExtendSelection
{
// If we're out of range, just return
if (([columns firstIndex] != CPNotFound && [columns firstIndex] < 0) || [columns lastIndex] >= [self numberOfColumns])
return;
// We deselect all rows when selecting columns.
if ([_selectedRowIndexes count] > 0)
{
[self _updateHighlightWithOldRows:_selectedRowIndexes newRows:[CPIndexSet indexSet]];
_selectedRowIndexes = [CPIndexSet indexSet];
}
var previousSelectedIndexes = [_selectedColumnIndexes copy];
if (shouldExtendSelection)
[_selectedColumnIndexes addIndexes:columns];
else
_selectedColumnIndexes = [columns copy];
[self _updateHighlightWithOldColumns:previousSelectedIndexes newColumns:_selectedColumnIndexes];
[self setNeedsDisplay:YES]; // FIXME: should be setNeedsDisplayInRect:enclosing rect of new (de)selected columns
// but currently -drawRect: is not implemented here
if (_headerView)
[_headerView setNeedsDisplay:YES];
[self _noteSelectionDidChange];
}
/*!
@ignore
*/
- (void)_setSelectedRowIndexes:(CPIndexSet)rows
{
if ([_selectedRowIndexes isEqualToIndexSet:rows])
return;
var previousSelectedIndexes = _selectedRowIndexes;
_lastSelectedRow = ([rows count] > 0) ? [rows lastIndex] : -1;
_selectedRowIndexes = [rows copy];
[self _updateHighlightWithOldRows:previousSelectedIndexes newRows:_selectedRowIndexes];
[self setNeedsDisplay:YES]; // FIXME: should be setNeedsDisplayInRect:enclosing rect of new (de)selected rows
// but currently -drawRect: is not implemented here
var binderClass = [[self class] _binderClassForBinding:@"selectionIndexes"];
[[binderClass getBinding:@"selectionIndexes" forObject:self] reverseSetValueFor:@"selectedRowIndexes"];
[self _noteSelectionDidChange];
}
/*!
Sets the row selection using indexes.
@param rows a CPIndexSet of rows to select
@param aFlag should extend the selection thereby retaining the previous selection
*/
- (void)selectRowIndexes:(CPIndexSet)rows byExtendingSelection:(BOOL)shouldExtendSelection
{
if ([rows isEqualToIndexSet:_selectedRowIndexes] ||
(([rows firstIndex] != CPNotFound && [rows firstIndex] < 0) || [rows lastIndex] >= [self numberOfRows]))
return;
// We deselect all columns when selecting rows.
if ([_selectedColumnIndexes count] > 0)
{
[self _updateHighlightWithOldColumns:_selectedColumnIndexes newColumns:[CPIndexSet indexSet]];
_selectedColumnIndexes = [CPIndexSet indexSet];
if (_headerView)
[_headerView setNeedsDisplay:YES];
}
var newSelectedIndexes;
if (shouldExtendSelection)
{
newSelectedIndexes = [_selectedRowIndexes copy];
[newSelectedIndexes addIndexes:rows];
}
else
newSelectedIndexes = [rows copy];
[self _setSelectedRowIndexes:newSelectedIndexes];
}
/*!
@ignore
*/
- (void)_updateHighlightWithOldRows:(CPIndexSet)oldRows newRows:(CPIndexSet)newRows
{
var firstExposedRow = [_exposedRows firstIndex],
exposedLength = [_exposedRows lastIndex] - firstExposedRow + 1,
deselectRows = [],
selectRows = [],
deselectRowIndexes = [oldRows copy],
selectRowIndexes = [newRows copy];
[deselectRowIndexes removeMatches:selectRowIndexes];
[deselectRowIndexes getIndexes:deselectRows maxCount:-1 inIndexRange:CPMakeRange(firstExposedRow, exposedLength)];
[selectRowIndexes getIndexes:selectRows maxCount:-1 inIndexRange:CPMakeRange(firstExposedRow, exposedLength)];
var showsSelection = _selectionHighlightStyle !== CPTableViewSelectionHighlightStyleNone,
selectors = [@selector(unsetThemeState:), @selector(setThemeState:)],
selectInfo = [
{ rows:deselectRows, selectorIndex:0 },
{ rows:selectRows, selectorIndex:showsSelection ? 1 : 0 }
];
for (var identifier in _dataViewsForTableColumns)
{
var dataViewsInTableColumn = _dataViewsForTableColumns[identifier]
for (var i = 0; i < selectInfo.length; ++i)
{
var info = selectInfo[i],
count = info.rows.length;
while (count--)
{
var view = dataViewsInTableColumn[info.rows[count]];
[view performSelector:selectors[info.selectorIndex] withObject:CPThemeStateSelectedDataView];
}
}
}
}
/*!
@ignore
*/
- (void)_updateHighlightWithOldColumns:(CPIndexSet)oldColumns newColumns:(CPIndexSet)newColumns
{
var firstExposedColumn = [_exposedColumns firstIndex],
exposedLength = [_exposedColumns lastIndex] - firstExposedColumn +1,
deselectColumns = [],
selectColumns = [],
deselectColumnIndexes = [oldColumns copy],
selectColumnIndexes = [newColumns copy],
selectRows = [];
[deselectColumnIndexes removeMatches:selectColumnIndexes];
[deselectColumnIndexes getIndexes:deselectColumns maxCount:-1 inIndexRange:CPMakeRange(firstExposedColumn, exposedLength)];
[selectColumnIndexes getIndexes:selectColumns maxCount:-1 inIndexRange:CPMakeRange(firstExposedColumn, exposedLength)];
[_exposedRows getIndexes:selectRows maxCount:-1 inIndexRange:nil];
var showsSelection = _selectionHighlightStyle !== CPTableViewSelectionHighlightStyleNone,
selectors = [@selector(unsetThemeState:), @selector(setThemeState:)],
// Rows do not show selection with CPTableViewSelectionHighlightStyleNone, but headers do
selectInfo = [
{
columns:deselectColumns,
rowSelectorIndex:0,
headerSelectorIndex:0
},
{
columns:selectColumns,
rowSelectorIndex:showsSelection ? 1 : 0,
headerSelectorIndex:1
}
],
rowsCount = selectRows.length;
for (var selectIndex = 0; selectIndex < selectInfo.length; ++selectIndex)
{
var info = selectInfo[selectIndex],
count = info.columns.length,
rowSelector = selectors[info.rowSelectorIndex],
headerSelector = selectors[info.headerSelectorIndex];
while (count--)
{
var columnIndex = info.columns[count],
identifier = [_tableColumns[columnIndex] UID],
dataViewsInTableColumn = _dataViewsForTableColumns[identifier];
for (var i = 0; i < rowsCount; i++)
{
var rowIndex = selectRows[i],
dataView = dataViewsInTableColumn[rowIndex];
[dataView performSelector:rowSelector withObject:CPThemeStateSelectedDataView];
}
if (_headerView)
{
var headerView = [_tableColumns[columnIndex] headerView];
[headerView performSelector:headerSelector withObject:CPThemeStateSelected];
}
}
}
}
/*!
Returns the index of the last selected column.
*/
- (int)selectedColumn
{
return [_selectedColumnIndexes lastIndex];
}
/*!
Returns an index set of all the selected columns.
*/
- (CPIndexSet)selectedColumnIndexes
{
return _selectedColumnIndexes;
}
/*!
Returns the index of the last selected row.
*/
- (int)selectedRow
{
return _lastSelectedRow;
}
/*!
Returns an index set with the indexes of all the selected rows.
*/
- (CPIndexSet)selectedRowIndexes
{
return [_selectedRowIndexes copy];
}
/*!
Deselects the column at a given index
@param anIndex the index of the column to deselect
*/
- (void)deselectColumn:(CPInteger)anIndex
{
var selectedColumnIndexes = [_selectedColumnIndexes copy];
[selectedColumnIndexes removeIndex:anIndex];
[self selectColumnIndexes:selectedColumnIndexes byExtendingSelection:NO];
[self _noteSelectionDidChange];
}
/*!
Deselects a row at a given index
@param aRow the row to deselect
*/
- (void)deselectRow:(CPInteger)aRow
{
var selectedRowIndexes = [_selectedRowIndexes copy];
[selectedRowIndexes removeIndex:aRow];
[self selectRowIndexes:selectedRowIndexes byExtendingSelection:NO];
[self _noteSelectionDidChange];
}
/*!
Returns the number of selected columns
*/
- (CPInteger)numberOfSelectedColumns
{
return [_selectedColumnIndexes count];
}
/*!
Returns the number of selected columns
*/
- (CPInteger)numberOfSelectedRows
{
return [_selectedRowIndexes count];
}
/*!
Returns YES if the column at a given index is selected, otherwise NO.
@param anIndex the index of a column
@return YES if the column is selected, otherwise NO.
*/
- (BOOL)isColumnSelected:(CPInteger)anIndex
{
return [_selectedColumnIndexes containsIndex:anIndex];
}
/*!
Returns YES if the row at a given index is selected, otherwise NO.
@param aRow the index of a row
@return YES if the row is selected, otherwise NO.
*/
- (BOOL)isRowSelected:(CPInteger)aRow
{
return [_selectedRowIndexes containsIndex:aRow];
}
/*!
@ignore
Deselects all rows and columns
*/
- (void)deselectAll
{
[self selectRowIndexes:[CPIndexSet indexSet] byExtendingSelection:NO];
[self selectColumnIndexes:[CPIndexSet indexSet] byExtendingSelection:NO];
}
- (void)selectAll:(id)sender
{
if (_allowsMultipleSelection)
{
if (_implementedDelegateMethods & CPTableViewDelegate_selectionShouldChangeInTableView_ &&
![_delegate selectionShouldChangeInTableView:self])
return;
if ([[self selectedColumnIndexes] count])
[self selectColumnIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfColumns])] byExtendingSelection:NO];
else
[self selectRowIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])] byExtendingSelection:NO];
}
}
- (void)deselectAll:(id)sender
{
if ([self allowsEmptySelection])
{
if (_implementedDelegateMethods & CPTableViewDelegate_selectionShouldChangeInTableView_ &&
![_delegate selectionShouldChangeInTableView:self])
return;
[self deselectAll];
}
}
/*!
Returns the number of columns in the table
*/
- (int)numberOfColumns
{
return NUMBER_OF_COLUMNS();
}
/*!
Returns the number of rows in the receiver.
*/
- (int)numberOfRows
{
if (_numberOfRows !== nil)
return _numberOfRows;
var contentBindingInfo = [self infoForBinding:@"content"];
if (contentBindingInfo)
{
var destination = [contentBindingInfo objectForKey:CPObservedObjectKey],
keyPath = [contentBindingInfo objectForKey:CPObservedKeyPathKey];
_numberOfRows = [[destination valueForKeyPath:keyPath] count];
}
else if (_dataSource && (_implementedDataSourceMethods & CPTableViewDataSource_numberOfRowsInTableView_))
_numberOfRows = [_dataSource numberOfRowsInTableView:self] || 0;
else
{
if (_dataSource)
CPLog(@"no content binding established and data source " + [_dataSource description] + " does not implement numberOfRowsInTableView:");
_numberOfRows = 0;
}
return _numberOfRows;
}
/*!
Edits the dataview at a given row and column. This method is usually invoked automatically and should rarely be invoked directly
The row at supplied rowIndex must be selected otherwise an exception is thrown.
@param columnIndex the index of the column to edit
@param rowIndex the index of the row to edit
@param theEvent the mouse event which triggers the edit, you can pass nil
@param flag YES if the dataview text should be selected, otherwise NO. (NOT YET IMPLEMENTED)
*/
- (void)editColumn:(CPInteger)columnIndex row:(CPInteger)rowIndex withEvent:(CPEvent)theEvent select:(BOOL)flag
{
// FIX ME: Cocoa documentation says all this should be called in THIS method:
// sets up the field editor, and sends selectWithFrame:inView:editor:delegate:start:length: and editWithFrame:inView:editor:delegate:event: to the field editor's NSCell object with the NSTableView as the text delegate.
if (_isViewBased)
{
var identifier = [_tableColumns[columnIndex] UID],
view = _dataViewsForTableColumns[identifier][rowIndex];
[[self window] makeFirstResponder:view];
}
else
{
if (![self isRowSelected:rowIndex])
[[CPException exceptionWithName:@"Error" reason:@"Attempt to edit row="+rowIndex+" when not selected." userInfo:nil] raise];
[self scrollRowToVisible:rowIndex];
[self scrollColumnToVisible:columnIndex];
// TODO Do something with flag.
_editingCellIndex = CGPointMake(columnIndex, rowIndex);
_editingCellIndex._shouldSelect = flag;
[self reloadDataForRowIndexes:[CPIndexSet indexSetWithIndex:rowIndex]
columnIndexes:[CPIndexSet indexSetWithIndex:columnIndex]];
}
}
/*!
Returns the column of the currently edited cell, or CPNotFound if none.
*/
- (CPInteger)editedColumn
{
if (!_editingCellIndex)
return CPNotFound;
return _editingCellIndex.x;
}
/*!
Returns the row of the currently edited cell, or CPNotFound if none.
*/
- (CPInteger)editedRow
{
if (!_editingCellIndex)
return CPNotFound;
return _editingCellIndex.y;
}
/*!
Returns the cornerview for the scrollview
*/
- (CPView)cornerView
{
return _cornerView;
}
/*!
Sets the cornerview for the scrollview
*/
- (void)setCornerView:(CPView)aView
{
if (_cornerView === aView)
return;
_cornerView = aView;
var scrollView = [self enclosingScrollView];
if ([scrollView isKindOfClass:[CPScrollView class]] && [scrollView documentView] === self)
[scrollView _updateCornerAndHeaderView];
}
/*!
Returns the headerview for the receiver. The headerview contains column headerviews for each table column.
*/
- (CPView)headerView
{
return _headerView;
}
/*!
Sets the headerview for the tableview. This is the container view for the table column header views.
This view also handles events for resizing and dragging.
If you don't want your tableview to have a headerview you should pass nil. (also see setCornerView:)
If you're looking to customize the header text of a column see CPTableColumn's -(CPView)headerView; method.
*/
- (void)setHeaderView:(CPView)aHeaderView
{
if (_headerView === aHeaderView)
return;
[_headerView setTableView:nil];
_headerView = aHeaderView;
if (_headerView)
{
[_headerView setTableView:self];
[_headerView setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), CGRectGetHeight([_headerView frame]))];
}
else
{
// If there is no header view, there should be no corner view
[_cornerView removeFromSuperview];
_cornerView = nil;
}
var scrollView = [self enclosingScrollView];
if ([scrollView isKindOfClass:[CPScrollView class]] && [scrollView documentView] === self)
[scrollView _updateCornerAndHeaderView];
[self setNeedsLayout];
}
// Complexity:
// O(Columns)
/*!
@ignore
*/
- (void)_recalculateTableColumnRanges
{
if (_dirtyTableColumnRangeIndex < 0)
return;
_numberOfHiddenColumns = 0;
var index = _dirtyTableColumnRangeIndex,
count = NUMBER_OF_COLUMNS(),
x = index === 0 ? 0.0 : CPMaxRange(_tableColumnRanges[index - 1]);
for (; index < count; ++index)
{
var tableColumn = _tableColumns[index];
if ([tableColumn isHidden])
{
_numberOfHiddenColumns += 1;
_tableColumnRanges[index] = CPMakeRange(x, 0.0);
}
else
{
var width = [_tableColumns[index] width] + _intercellSpacing.width;
_tableColumnRanges[index] = CPMakeRange(x, width);
x += width;
}
}
_tableColumnRanges.length = count;
_dirtyTableColumnRangeIndex = CPNotFound;
}
// Complexity:
// O(1)
/*!
Returns a CGRect with the location and size of the column
If aColumnIndex lies outside the range of the table columns a CGZeroRect is returned
@param aColumnIndex the index of the column to return the rect of
*/
- (CGRect)rectOfColumn:(CPInteger)aColumnIndex
{
// Convert e.g. "0" to 0.
aColumnIndex = +aColumnIndex;
if (aColumnIndex < 0 || aColumnIndex >= NUMBER_OF_COLUMNS())
return CGRectMakeZero();
var column = [[self tableColumns] objectAtIndex:aColumnIndex];
if ([column isHidden])
return CGRectMakeZero();
UPDATE_COLUMN_RANGES_IF_NECESSARY();
var range = _tableColumnRanges[aColumnIndex];
return CGRectMake(range.location, 0.0, range.length, CGRectGetHeight([self bounds]));
}
// Complexity:
// O(1)
/*!
@ignore
Returns a CGRect with the location and size of the row
@param aRowIndex the index of the row to return the rect of
@param checkRange if YES this method will return a zero rect if the aRowIndex is outside of the range of valid indices
*/
- (CGRect)_rectOfRow:(CPInteger)aRowIndex checkRange:(BOOL)checkRange
{
var lastIndex = [self numberOfRows] - 1;
if (checkRange && (aRowIndex > lastIndex || aRowIndex < 0))
return CGRectMakeZero();
if (_implementedDelegateMethods & CPTableViewDelegate_tableView_heightOfRow_)
{
var rowToLookUp = MIN(aRowIndex, lastIndex);
// if the row doesn't exist
if (rowToLookUp !== CPNotFound)
{
var y = _cachedRowHeights[rowToLookUp].heightAboveRow,
height = _cachedRowHeights[rowToLookUp].height + _intercellSpacing.height,
rowDelta = aRowIndex - rowToLookUp;
}
else
{
y = aRowIndex * (_rowHeight + _intercellSpacing.height);
height = _rowHeight + _intercellSpacing.height;
}
// if we need the rect of a row past the last index
if (rowDelta > 0)
{
y += rowDelta * (_rowHeight + _intercellSpacing.height);
height = _rowHeight + _intercellSpacing.height;
}
}
else
{
var y = aRowIndex * (_rowHeight + _intercellSpacing.height),
height = _rowHeight + _intercellSpacing.height;
}
return CGRectMake(0.0, y, CGRectGetWidth([self bounds]), height);
}
/*!
Returns a CGRect with the location and size of the row. CGRectZero is returned if aRowIndex doesn't exist.
@param aRowIndex the index of the row you want the rect of
*/
- (CGRect)rectOfRow:(CPInteger)aRowIndex
{
return [self _rectOfRow:aRowIndex checkRange:YES];
}
// Complexity:
// O(1)
/*!
Returns a range of indices for the rows that lie wholly or partially within the vertical boundaries of a given rectangle.
@param aRect A rectangle in the coordinate system of the receiver.
*/
- (CPRange)rowsInRect:(CGRect)aRect
{
// If we have no rows, then we won't intersect anything.
if (_numberOfRows <= 0)
return CPMakeRange(0, 0);
var bounds = [self bounds];
// No rows if the rect doesn't even intersect us.
if (!CGRectIntersectsRect(aRect, bounds))
return CPMakeRange(0, 0);
var firstRow = [self rowAtPoint:aRect.origin];
// first row has to be undershot, because if not we wouldn't be intersecting.
if (firstRow < 0)
firstRow = 0;
var lastRow = [self rowAtPoint:CGPointMake(0.0, CGRectGetMaxY(aRect))];
// last row has to be overshot, because if not we wouldn't be intersecting.
if (lastRow < 0)
lastRow = _numberOfRows - 1;
return CPMakeRange(firstRow, lastRow - firstRow + 1);
}
/*!
@ignore
When we draw the row backgrounds we don't want an index bounding our range.
*/
- (CPRange)_unboundedRowsInRect:(CGRect)aRect
{
var boundedRange = [self rowsInRect:aRect],
lastRow = CPMaxRange(boundedRange),
rectOfLastRow = [self _rectOfRow:lastRow checkRange:NO],
bottom = CGRectGetMaxY(aRect),
bottomOfBoundedRows = CGRectGetMaxY(rectOfLastRow);
// we only have to worry about the rows below the last...
if (bottom <= bottomOfBoundedRows)
return boundedRange;
var numberOfNewRows = CEIL(bottom - bottomOfBoundedRows) / ([self rowHeight] + _intercellSpacing.height);
boundedRange.length += numberOfNewRows + 1;
return boundedRange;
}
// Complexity:
// O(lg Columns) if table view contains no hidden columns
// O(Columns) if table view contains hidden columns
/*!
Returns the indexes of the receiver's columns that intersect the specified rectangle.
@param aRect a rectangle in the coordinate system of the receiver.
*/
- (CPIndexSet)columnIndexesInRect:(CGRect)aRect
{
var column = MAX(0, [self columnAtPoint:CGPointMake(aRect.origin.x, 0.0)]),
lastColumn = [self columnAtPoint:CGPointMake(CGRectGetMaxX(aRect), 0.0)];
if (lastColumn === CPNotFound)
lastColumn = NUMBER_OF_COLUMNS() - 1;
// Don't bother doing the expensive removal of hidden indexes if we have no hidden columns.
if (_numberOfHiddenColumns <= 0)
return [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(column, lastColumn - column + 1)];
//
var indexSet = [CPIndexSet indexSet];
for (; column <= lastColumn; ++column)
{
var tableColumn = _tableColumns[column];
if (![tableColumn isHidden])
[indexSet addIndex:column];
}
return indexSet;
}
// Complexity:
// O(lg Columns) if table view contains now hidden columns
// O(Columns) if table view contains hidden columns
/*!
Returns the index of a column at a given point. If no column is there CPNotFound is returned.
@param aPoint a CGPoint
*/
- (CPInteger)columnAtPoint:(CGPoint)aPoint
{
var bounds = [self bounds];
if (!CGRectContainsPoint(bounds, aPoint))
return CPNotFound;
UPDATE_COLUMN_RANGES_IF_NECESSARY();
var x = aPoint.x,
low = 0,
high = _tableColumnRanges.length - 1;
while (low <= high)
{
var middle = FLOOR(low + (high - low) / 2),
range = _tableColumnRanges[middle];
if (x < range.location)
high = middle - 1;
else if (x >= CPMaxRange(range))
low = middle + 1;
else
{
var numberOfColumns = _tableColumnRanges.length;
while (middle < numberOfColumns && [_tableColumns[middle] isHidden])
++middle;
if (middle < numberOfColumns)
return middle;
return CPNotFound;
}
}
return CPNotFound;
}
//Complexity
// O(1) for static row height
// 0(lg Rows) for variable row heights
/*!
Returns the index of a row at a particular point. If no row exists CPNotFound is returned.
@param aPoint a CGPoint
*/
- (CPInteger)rowAtPoint:(CGPoint)aPoint
{
if (_implementedDelegateMethods & CPTableViewDelegate_tableView_heightOfRow_)
{
return [_cachedRowHeights indexOfObject:aPoint
inSortedRange:nil
options:0
usingComparator:function(aPoint, rowCache)
{
var upperBound = rowCache.heightAboveRow;
if (aPoint.y < upperBound)
return CPOrderedAscending;
if (aPoint.y > upperBound + rowCache.height + _intercellSpacing.height)
return CPOrderedDescending;
return CPOrderedSame;
}];
}
var y = aPoint.y,
row = FLOOR(y / (_rowHeight + _intercellSpacing.height));
if (row >= _numberOfRows)
return CPNotFound;
return row;
}
/*!
Returns the index of the row for the specified view.
@param view The view.
@return The index of the row corresponding to the view. Returns -1 if the view is not a dataView, or a subview of a dataView.
@discussion This is typically needed in the action method for a CPButton (or CPControl) to find out what row (and column) the action should be performed on.
The implementation is O(rxc) where r is the number of visible rows, and c is the number of visible columns, so this method should generally not be called within a loop.
*/
- (CPInteger)rowForView:(CPView)aView
{
return [self rowNotColumn:YES forView:aView];
}
/*!
Returns the index of the column for the specified view.
@param view The view.
@return The index of the column corresponding to the view. Returns -1 if the view is not a dataView, or a subview of a dataView.
@discussion This is typically needed in the action method for a CPButton (or CPControl) to find out what row (and column) the action should be performed on.
The implementation is O(rxc) where r is the number of visible rows, and c is the number of visible columns, so this method should generally not be called within a loop.
*/
- (CPInteger)columnForView:(CPView)aView
{
return [self rowNotColumn:NO forView:aView];
}
/*!
@ignore
*/
- (CPInteger)rowNotColumn:(BOOL)isRow forView:(CPView)aView
{
if (![aView isKindOfClass:[CPView class]])
return -1;
var cellView = aView,
contentView = [[self window] contentView];
while (1)
{
if (cellView == contentView)
{
return -1;
}
else
{
var superview = [cellView superview];
if ([superview isKindOfClass:[CPTableView class]])
{
break;
}
cellView = superview;
}
}
var exposedRows = [],
exposedColumns = [];
[_exposedRows getIndexes:exposedRows maxCount:-1 inIndexRange:nil];
[_exposedColumns getIndexes:exposedColumns maxCount:-1 inIndexRange:nil];
var colcount = exposedColumns.length,
countOfRows = exposedRows.length;
while (colcount--)
{
var column = exposedColumns[colcount],
tableColumnUID = [_tableColumns[column] UID],
dataViewsInTableColumn = _dataViewsForTableColumns[tableColumnUID],
rowcount = countOfRows;
while (rowcount--)
{
var row = exposedRows[rowcount];
if (cellView == dataViewsInTableColumn[row])
return isRow ? row : column;
}
}
return -1;
}
/*!
Returns a rect for the dataview / cell at the column and row given.
If the column or row index is greater than the number of columns or rows a CGZeroRect is returned
@param aColumn index of the column
@param aRow index of the row
*/
- (CGRect)frameOfDataViewAtColumn:(CPInteger)aColumn row:(CPInteger)aRow
{
UPDATE_COLUMN_RANGES_IF_NECESSARY();
if (aColumn > [self numberOfColumns] || aRow > [self numberOfRows])
return CGRectMakeZero();
var tableColumnRange = _tableColumnRanges[aColumn],
rectOfRow = [self rectOfRow:aRow],
leftInset = FLOOR(_intercellSpacing.width / 2.0),
topInset = FLOOR(_intercellSpacing.height / 2.0);
return CGRectMake(tableColumnRange.location + leftInset, CGRectGetMinY(rectOfRow) + topInset, tableColumnRange.length - _intercellSpacing.width, CGRectGetHeight(rectOfRow) - _intercellSpacing.height);
}
/*!
@ignore
*/
- (void)resizeWithOldSuperviewSize:(CGSize)aSize
{
[super resizeWithOldSuperviewSize:aSize];
if (_disableAutomaticResizing)
return;
var mask = _columnAutoResizingStyle;
// should we actually do some resizing?
if (!_lastColumnShouldSnap)
{
// did the clip view intersect the old tablesize?
var superview = [self superview];
if (!superview || ![superview isKindOfClass:[CPClipView class]])
return;
var superviewWidth = [superview bounds].size.width,
lastColumnMaxX = CGRectGetMaxX([self rectOfColumn:[self numberOfColumns] -1]);
// Fix me: this fires on the table setup at times
if (lastColumnMaxX >= superviewWidth && lastColumnMaxX <= aSize.width || lastColumnMaxX <= superviewWidth && lastColumnMaxX >= aSize.width)
_lastColumnShouldSnap = YES;
else if (mask === CPTableViewUniformColumnAutoresizingStyle)
return;
}
if (mask === CPTableViewUniformColumnAutoresizingStyle)
[self _resizeAllColumnUniformlyWithOldSize:aSize];
else if (mask === CPTableViewLastColumnOnlyAutoresizingStyle)
[self sizeLastColumnToFit];
else if (mask === CPTableViewFirstColumnOnlyAutoresizingStyle)
[self _autoResizeFirstColumn];
}
/*!
@ignore
*/
- (void)_autoResizeFirstColumn
{
var superview = [self superview];
if (!superview)
return;
UPDATE_COLUMN_RANGES_IF_NECESSARY();
var count = NUMBER_OF_COLUMNS(),
columnToResize = nil,
totalWidth = 0,
i = 0;
for (; i < count; i++)
{
var column = _tableColumns[i];
if (![column isHidden])
{
if (!columnToResize)
columnToResize = column;
totalWidth += [column width] + _intercellSpacing.width;
}
}
// If there is a visible column
if (columnToResize)
{
var superviewSize = [superview bounds].size,
newWidth = superviewSize.width - totalWidth;
newWidth += [columnToResize width];
[columnToResize _tryToResizeToWidth:newWidth];
}
[self setNeedsLayout];
}
/*!
@ignore
FIX ME: this can be a lot faster
*/
- (void)_resizeAllColumnUniformlyWithOldSize:(CGSize)oldSize
{
// what we care about is the superview clip rect
// FIX ME: if it's not in a scrollview this doesn't really work
var superview = [self superview];
if (!superview || ![superview isKindOfClass:[CPClipView class]])
return;
UPDATE_COLUMN_RANGES_IF_NECESSARY();
var superviewWidth = [superview bounds].size.width,
count = NUMBER_OF_COLUMNS(),
resizableColumns = [CPIndexSet indexSet],
remainingSpace = 0.0,
i = 0;
// find resizable columns
// FIX ME: we could cache resizableColumns after this loop and reuse it during the resize
for (; i < count; i++)
{
var tableColumn = _tableColumns[i];
if (![tableColumn isHidden] && ([tableColumn resizingMask] & CPTableColumnAutoresizingMask))
[resizableColumns addIndex:i];
}
var maxXofColumns = CGRectGetMaxX([self rectOfColumn:[resizableColumns lastIndex]]),
remainingSpace = superviewWidth - maxXofColumns,
resizeableColumnsCount = [resizableColumns count],
proportionate = 0;
while (remainingSpace && resizeableColumnsCount)
{
// Divy out the space.
proportionate += remainingSpace / resizeableColumnsCount;
// Reset the remaining space to 0
remainingSpace = 0.0;
var index = CPNotFound;
while ((index = [resizableColumns indexGreaterThanIndex:index]) !== CPNotFound)
{
var item = _tableColumns[index],
proposedWidth = [item width] + proportionate,
resizeLeftovers = [item _tryToResizeToWidth:proposedWidth];
if (resizeLeftovers)
{
[resizableColumns removeIndex:index];
remainingSpace += resizeLeftovers;
}
}
}
// now that we've reached the end we know there are likely rounding errors
// so we should size the last resized to fit
// find the last visisble column
while (count-- && [_tableColumns[count] isHidden]);
// find the max x, but subtract a single pixel since the spacing isn't applicable here.
var delta = superviewWidth - CGRectGetMaxX([self rectOfColumn:count]) - ([self intercellSpacing].width || 1),
newSize = [item width] + delta;
[item _tryToResizeToWidth:newSize];
}
/*!
Sets the column autoresizing style of the receiver to a given style.
@param aStyle the column autoresizing style for the receiver. Valid values are:
<pre>
CPTableViewNoColumnAutoresizing
CPTableViewUniformColumnAutoresizingStyle
CPTableViewLastColumnOnlyAutoresizingStyle
CPTableViewFirstColumnOnlyAutoresizingStyle
</pre>
*/
- (void)setColumnAutoresizingStyle:(unsigned)style
{
//FIX ME: CPTableViewSequentialColumnAutoresizingStyle and CPTableViewReverseSequentialColumnAutoresizingStyle are not yet implemented
_columnAutoResizingStyle = style;
}
/*!
Returns the column auto resizing style of the receiver.
*/
- (unsigned)columnAutoresizingStyle
{
return _columnAutoResizingStyle;
}
/*!
Resizes the last column if there's room so the receiver fits exactly within its enclosing clip view.
*/
- (void)sizeLastColumnToFit
{
_lastColumnShouldSnap = YES;
var superview = [self superview];
if (!superview)
return;
var superviewSize = [superview bounds].size;
UPDATE_COLUMN_RANGES_IF_NECESSARY();
var count = NUMBER_OF_COLUMNS();
// Decrement the counter until we get to the last column that's not hidden
while (count-- && [_tableColumns[count] isHidden]);
// If the last column exists
if (count >= 0)
{
var columnToResize = _tableColumns[count],
newSize = MAX(0.0, superviewSize.width - CGRectGetMinX([self rectOfColumn:count]) - _intercellSpacing.width);
[columnToResize _tryToResizeToWidth:newSize];
}
[self setNeedsLayout];
}
/*!
Informs the receiver that the number of records in the datasource has changed.
*/
- (void)noteNumberOfRowsChanged
{
var oldNumberOfRows = _numberOfRows;
_numberOfRows = nil;
_cachedRowHeights = [];
// this line serves two purposes
// 1. it updates the _numberOfRows cache with the -numberOfRows call
// 2. it updates the row height cache if needed
[self noteHeightOfRowsWithIndexesChanged:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])]];
// remove row indexes from the selection if they no longer exist
var hangingSelections = oldNumberOfRows - _numberOfRows;
if (hangingSelections > 0)
{
var previousSelectionCount = [_selectedRowIndexes count];
[_selectedRowIndexes removeIndexesInRange:CPMakeRange(_numberOfRows, hangingSelections)];
if (![_selectedRowIndexes containsIndex:[self selectedRow]])
_lastSelectedRow = CPNotFound;
// For optimal performance, only send a notification if indices were actually removed.
if (previousSelectionCount > [_selectedRowIndexes count])
[self _noteSelectionDidChange];
}
[self tile];
}
/*!
Informs the receiver that the rows specified in indexSet have changed height.
@param anIndexSet an index set containing the indexes of the rows which changed height
*/
- (void)noteHeightOfRowsWithIndexesChanged:(CPIndexSet)anIndexSet
{
if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_heightOfRow_))
return;
// this method will update the height of those rows, but since the cached array also contains
// the height above the row it needs to recalculate for the rows below it too
var i = [anIndexSet firstIndex],
count = _numberOfRows - i,
heightAbove = (i > 0) ? _cachedRowHeights[i - 1].height + _cachedRowHeights[i - 1].heightAboveRow + _intercellSpacing.height : 0;
for (; i < count; i++)
{
// update the cache if the user told us to
if ([anIndexSet containsIndex:i])
var height = [_delegate tableView:self heightOfRow:i];
_cachedRowHeights[i] = {"height":height, "heightAboveRow":heightAbove};
heightAbove += height + _intercellSpacing.height;
}
}
/*!
Lays out the dataviews and resizes the tableview so that everything fits.
*/
- (void)tile
{
UPDATE_COLUMN_RANGES_IF_NECESSARY();
var width = _tableColumnRanges.length > 0 ? CPMaxRange([_tableColumnRanges lastObject]) : 0.0,
superview = [self superview];
if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_heightOfRow_))
var height = (_rowHeight + _intercellSpacing.height) * _numberOfRows;
else if ([self numberOfRows] === 0)
var height = 0;
else
{
// if this is the fist run we need to populate the cache
if ([self numberOfRows] !== _cachedRowHeights.length)
[self noteHeightOfRowsWithIndexesChanged:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])]];
var heightObject = _cachedRowHeights[_cachedRowHeights.length - 1],
height = heightObject.heightAboveRow + heightObject.height + _intercellSpacing.height;
}
if ([superview isKindOfClass:[CPClipView class]])
{
var superviewSize = [superview bounds].size;
width = MAX(superviewSize.width, width);
height = MAX(superviewSize.height, height);
}
[self setFrameSize:CGSizeMake(width, height)];
[self setNeedsLayout];
[self setNeedsDisplay:YES];
}
/*!
Scrolls the receiver vertically in an enclosing CPClipView so the row specified by rowIndex is visible.
@param aRowIndex the index of the row to scroll to.
*/
- (void)scrollRowToVisible:(int)rowIndex
{
var visible = [self visibleRect],
rowRect = [self rectOfRow:rowIndex];
visible.origin.y = rowRect.origin.y;
visible.size.height = rowRect.size.height;
[self scrollRectToVisible:visible];
}
/*!
Scrolls the receiver and header view horizontally in an enclosing CPClipView so the column specified by columnIndex is visible.
@param aColumnIndex the index of the column to scroll to.
*/
- (void)scrollColumnToVisible:(int)columnIndex
{
var visible = [self visibleRect],
colRect = [self rectOfColumn:columnIndex];
visible.origin.x = colRect.origin.x;
visible.size.width = colRect.size.width;
[self scrollRectToVisible:visible];
[_headerView scrollRectToVisible:colRect];
}
/*!
Set the name under which the table information is automatically saved to theAutosaveName.
The table information is saved separately for each user and for each application that user uses.
@note Even though a table view has an autosave name, it may not be saving table information automatically.
To set whether table information is being saved automatically, use \c setAutosaveTableColumns:
*/
- (void)setAutosaveName:(CPString)theAutosaveName
{
if (_autosaveName === theAutosaveName)
return;
_autosaveName = theAutosaveName;
[self setAutosaveTableColumns:!!theAutosaveName];
[self _restoreFromAutosave];
}
/*!
Returns the autosave name of the tableview.
*/
- (CPString)autosaveName
{
return _autosaveName;
}
/*!
Sets whether the order and width of this table view's columns are automatically saved.
The table information is saved separately for each user and for each application that user uses.
@note that if autosaveName returns nil, this setting is ignored and table information isn't saved.
*/
- (void)setAutosaveTableColumns:(BOOL)shouldAutosave
{
_autosaveTableColumns = shouldAutosave;
}
/*!
Returns YES the table columns should autosave, otherwise NO.
*/
- (BOOL)autosaveTableColumns
{
return _autosaveTableColumns;
}
/*!
@ignore
*/
- (CPString)_columnsKeyForAutosaveName:(CPString)theAutosaveName
{
return @"CPTableView Columns " + theAutosaveName;
}
/*!
@ignore
*/
- (BOOL)_autosaveEnabled
{
return [self autosaveName] && [self autosaveTableColumns];
}
/*!
@ignore
Stores the tablecolumn setup in user defaults.
I believe Apple stores the entire encoded table column,
in our case that seems overkill since we need to store everything in a cookie.
*/
- (void)_autosave
{
if (![self _autosaveEnabled])
return;
var userDefaults = [CPUserDefaults standardUserDefaults],
autosaveName = [self autosaveName];
var columns = [self tableColumns],
columnsSetup = [];
for (var i = 0; i < [columns count]; i++)
{
var column = [columns objectAtIndex:i],
metaData = @{
@"identifier": [column identifier],
@"width": [column width]
};
[columnsSetup addObject:metaData];
}
[userDefaults setObject:columnsSetup forKey:[self _columnsKeyForAutosaveName:autosaveName]];
}
/*!
@ignore
*/
- (void)_restoreFromAutosave
{
if (![self _autosaveEnabled])
return;
var userDefaults = [CPUserDefaults standardUserDefaults],
autosaveName = [self autosaveName],
tableColumns = [userDefaults objectForKey:[self _columnsKeyForAutosaveName:autosaveName]];
if ([tableColumns count] != [[self tableColumns] count])
return;
for (var i = 0; i < [tableColumns count]; i++)
{
var metaData = [tableColumns objectAtIndex:i],
columnIdentifier = [metaData objectForKey:@"identifier"],
column = [self columnWithIdentifier:columnIdentifier],
tableColumn = [self tableColumnWithIdentifier:columnIdentifier];
if (tableColumn && column != CPNotFound)
{
[self _moveColumn:column toColumn:i];
[tableColumn setWidth:[metaData objectForKey:@"width"]];
}
}
}
/*!
@anchor setdelegate
Sets the delegate of the receiver.
@param aDelegate the delegate object for the tableview.
The delegate can provide notification for user interaction, display behaviour, contextual menus, and more.
@section Providing Views for Rows and Columns:
Returns the view used to display the specified row and column.
@code
- (CPView)tableView:(CPTableView)tableView viewForTableColumn:(CPTableColumn)tableColumn row:(CPInteger)row
@endcode
@param
The view to display the specified column and row. Returning nil is acceptable, and a view will not be shown at that location.
@discussion
This method is required if you wish to use a view-based table view in Interface builder or cell-based table views without using CPTableColumn's setDataView: method.
It is recommended that the implementation of this method first call the CPTableView method makeViewWithIdentifier:owner: passing, respectively, the tableColumn parameter’s identifier and self as the owner to attempt to reuse a view that is no longer visible. The frame of the view returned by this method is not important, and it will be automatically set by the table.
The view's properties should be properly set up before returning the result. The delegate do not need to implement tableview:objectValueforTableColumn:row:.
When using bindings, this method is optional if at least one identifier has been associated with the table view at design time. If this method is not implemented, the table will automatically call the CPTableView method makeViewWithIdentifier:owner: with the tableColumn parameter’s identifier and the table view’s delegate respectively as parameters, to attempt to reuse a previous view, or automatically unarchive a prototype associated with the table view.
The autoresizingMask of the returned view will automatically be set to CPViewNotSizable if the data view was created in Interface builder in a table column. Otherwise, for example if the view is created manually in code, this method expects the data view to have a CPViewNotSizable mask.
@section displayingcells Displaying Cells:
Called when the tableview is about to display a dataview
@code
- (void)tableView:(CPTableView)aTableView willDisplayView:(id)aView forTableColumn:(CPTableColumn)aTableColumn row:(int)rowIndex;
@endcode
Group rows are a way to separate a groups of data in a tableview. Return YES if the given row is a group row, otherwise NO.
@code
- (BOOL)tableView:(CPTableView)tableView isGroupRow:(int)row;
@endcode
@section editingcells Editing Cells:
Return YES if the dataview at a given index and column should be edited, otherwise NO.
@code
- (BOOL)tableView:(CPTableView)aTableView shouldEditTableColumn:(CPTableColumn)aTableColumn row:(int)rowIndex;
@endcode
@section sizes Setting Row and Column Size:
Return the height of the row at a given index. Only implement this if you want variable row heights. Otherwise use setRowHeight: on the tableview.
@code
- (float)tableView:(CPTableView)tableView heightOfRow:(int)row;
@endcode
@section selection Selecting in the TableView:
@note These methods are only called when the user does something.@endnote
Return YES if the selection of the tableview should change, otherwise NO to keep the current selection.
@code
- (BOOL)selectionShouldChangeInTableView:(CPTableView)aTableView;
@endcode
Return YES if the row at a given index should be selected, other NO to deny the selection.
@code
- (BOOL)tableView:(CPTableView)aTableView shouldSelectRow:(int)rowIndex;
@endcode
Return YES if the table column given should be selected, otherwise NO to deny the selection.
@code
- (BOOL)tableView:(CPTableView)aTableView shouldSelectTableColumn:(CPTableColumn)aTableColumn;
@endcode
Informs the delegate that the tableview is in the process of chaining the selection.
This usually happens when the user is dragging their mouse across rows.
@code
- (void)tableViewSelectionIsChanging:(CPNotification)aNotification
@endcode
Informs the delegate that the tableview selection has changed.
@code
- (void)tableViewSelectionDidChange:(CPNotification)aNotification;
@endcode
@section movingandresizingcolumns Moving and Resizing Columns:
Return YES if the column at a given index should move to a new column index, otherwise NO.
@code
- (BOOL)tableView:(CPTableView)tableView shouldReorderColumn:(int)columnIndex toColumn:(int)newColumnIndex;
@endcode
Notifies the delegate that the tableview drag occurred. This is sent on mouse up.
@code
- (void)tableView:(CPTableView)tableView didDragTableColumn:(CPTableColumn)tableColumn;
@endcode
Notifies the delegate that a table column was moved by the user.
@code
- (void)tableViewColumnDidMove:(CPNotification)aNotification;
@endcode
Notifies the delegate that the user resized the table column
@code
- (void)tableViewColumnDidResize:(CPNotification)aNotification;
@endcode
@section mousevents Responding to Mouse Events:
Sent when the user clicks a table column but doesn't drag.
@code
- (void)tableView:(CPTableView)tableView didClickTableColumn:(CPTableColumn)tableColumn;
@endcode
Notify the delegate that the user has clicked the table header of a column.
@code
- (void)tableView:(CPTableView)tableView mouseDownInHeaderOfTableColumn:(CPTableColumn)tableColumn;
@endcode
@section contextualmenus Contextual Menus:
Called when the user right-clicks on the tableview. -1 is passed for the row or column if the user doesn't right click on a real row or column
Return a CPMenu that should be displayed if the user right-clicks. If you do not implement this the tableview will call super on menuForEvent
@code
- (CPMenu)tableView:(CPTableView)aTableView menuForTableColumn:(CPTableColumn)aColumn row:(int)aRow;
@endcode
@section deletekey Delete Key
Called when the user presses the delete key. Many times you will want to delete data (or prompt for deletion) when the user presses the delete key.
Your delegate can implement this method to avoid subclassing the tableview to add this behaviour.
@code
- (void)tableViewDeleteKeyPressed:(CPTableView)aTableView;
@endcode
*/
- (void)setDelegate:(id)aDelegate
{
if (_delegate === aDelegate)
return;
var defaultCenter = [CPNotificationCenter defaultCenter];
if (_delegate)
{
if ([_delegate respondsToSelector:@selector(tableViewColumnDidMove:)])
[defaultCenter
removeObserver:_delegate
name:CPTableViewColumnDidMoveNotification
object:self];
if ([_delegate respondsToSelector:@selector(tableViewColumnDidResize:)])
[defaultCenter
removeObserver:_delegate
name:CPTableViewColumnDidResizeNotification
object:self];
if ([_delegate respondsToSelector:@selector(tableViewSelectionDidChange:)])
[defaultCenter
removeObserver:_delegate
name:CPTableViewSelectionDidChangeNotification
object:self];
if ([_delegate respondsToSelector:@selector(tableViewSelectionIsChanging:)])
[defaultCenter
removeObserver:_delegate
name:CPTableViewSelectionIsChangingNotification
object:self];
}
_delegate = aDelegate;
_implementedDelegateMethods = 0;
if ([_delegate respondsToSelector:@selector(selectionShouldChangeInTableView:)])
_implementedDelegateMethods |= CPTableViewDelegate_selectionShouldChangeInTableView_;
if ([_delegate respondsToSelector:@selector(tableView:viewForTableColumn:row:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_viewForTableColumn_row_;
else if ([_delegate respondsToSelector:@selector(tableView:dataViewForTableColumn:row:)])
{
_implementedDelegateMethods |= CPTableViewDelegate_tableView_dataViewForTableColumn_row_;
CPLog.warn("tableView:dataViewForTableColumn: is deprecated. You should use -tableView:viewForTableColumn: where you can request the view with -makeViewWithIdentifier:owner:");
}
[self _updateIsViewBased];
if ([_delegate respondsToSelector:@selector(tableView:didClickTableColumn:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_didClickTableColumn_;
if ([_delegate respondsToSelector:@selector(tableView:didDragTableColumn:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_didDragTableColumn_;
if ([_delegate respondsToSelector:@selector(tableView:heightOfRow:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_heightOfRow_;
if ([_delegate respondsToSelector:@selector(tableView:isGroupRow:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_isGroupRow_;
if ([_delegate respondsToSelector:@selector(tableView:mouseDownInHeaderOfTableColumn:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_mouseDownInHeaderOfTableColumn_;
if ([_delegate respondsToSelector:@selector(tableView:nextTypeSelectMatchFromRow:toRow:forString:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_nextTypeSelectMatchFromRow_toRow_forString_;
if ([_delegate respondsToSelector:@selector(tableView:selectionIndexesForProposedSelection:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_selectionIndexesForProposedSelection_;
if ([_delegate respondsToSelector:@selector(tableView:shouldEditTableColumn:row:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldEditTableColumn_row_;
if ([_delegate respondsToSelector:@selector(tableView:shouldSelectRow:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldSelectRow_;
if ([_delegate respondsToSelector:@selector(tableView:shouldSelectTableColumn:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldSelectTableColumn_;
if ([_delegate respondsToSelector:@selector(tableView:shouldShowViewExpansionForTableColumn:row:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldShowViewExpansionForTableColumn_row_;
if ([_delegate respondsToSelector:@selector(tableView:shouldTrackView:forTableColumn:row:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldTrackView_forTableColumn_row_;
if ([_delegate respondsToSelector:@selector(tableView:shouldTypeSelectForEvent:withCurrentSearchString:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldTypeSelectForEvent_withCurrentSearchString_;
if ([_delegate respondsToSelector:@selector(tableView:toolTipForView:rect:tableColumn:row:mouseLocation:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_toolTipForView_rect_tableColumn_row_mouseLocation_;
if ([_delegate respondsToSelector:@selector(tableView:typeSelectStringForTableColumn:row:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_typeSelectStringForTableColumn_row_;
if ([_delegate respondsToSelector:@selector(tableView:willDisplayView:forTableColumn:row:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableView_willDisplayView_forTableColumn_row_;
if ([_delegate respondsToSelector:@selector(tableView:menuForTableColumn:row:)])
_implementedDelegateMethods |= CPTableViewDelegate_tableViewMenuForTableColumn_Row_;
if ([_delegate respondsToSelector:@selector(tableViewColumnDidMove:)])
[defaultCenter
addObserver:_delegate
selector:@selector(tableViewColumnDidMove:)
name:CPTableViewColumnDidMoveNotification
object:self];
if ([_delegate respondsToSelector:@selector(tableViewColumnDidResize:)])
[defaultCenter
addObserver:_delegate
selector:@selector(tableViewColumnDidResize:)
name:CPTableViewColumnDidResizeNotification
object:self];
if ([_delegate respondsToSelector:@selector(tableViewSelectionDidChange:)])
[defaultCenter
addObserver:_delegate
selector:@selector(tableViewSelectionDidChange:)
name:CPTableViewSelectionDidChangeNotification
object:self];
if ([_delegate respondsToSelector:@selector(tableViewSelectionIsChanging:)])
[defaultCenter
addObserver:_delegate
selector:@selector(tableViewSelectionIsChanging:)
name:CPTableViewSelectionIsChangingNotification
object:self];
}
/*!
Returns the delegate object for the table view.
*/
- (id)delegate
{
return _delegate;
}
/*!
@ignore
*/
- (void)_sendDelegateDidClickColumn:(int)column
{
if (_implementedDelegateMethods & CPTableViewDelegate_tableView_didClickTableColumn_)
[_delegate tableView:self didClickTableColumn:_tableColumns[column]];
}
/*!
@ignore
*/
- (void)_sendDelegateDidDragColumn:(int)column
{
if (_implementedDelegateMethods & CPTableViewDelegate_tableView_didDragTableColumn_)
[_delegate tableView:self didDragTableColumn:_tableColumns[column]];
}
- (void)_sendDelegateDidMouseDownInHeader:(int)column
{
if (_implementedDelegateMethods & CPTableViewDelegate_tableView_mouseDownInHeaderOfTableColumn_)
[_delegate tableView:self mouseDownInHeaderOfTableColumn:_tableColumns[column]];
}
/*
@ignore
*/
- (BOOL)_sendDelegateDeleteKeyPressed
{
if ([_delegate respondsToSelector: @selector(tableViewDeleteKeyPressed:)])
{
[_delegate tableViewDeleteKeyPressed:self];
return YES;
}
return NO;
}
/*!
@ignore
*/
- (void)_sendDataSourceSortDescriptorsDidChange:(CPArray)oldDescriptors
{
if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_sortDescriptorsDidChange_)
[_dataSource tableView:self sortDescriptorsDidChange:oldDescriptors];
}
/*!
@ignore
*/
- (void)_didClickTableColumn:(int)clickedColumn modifierFlags:(unsigned)modifierFlags
{
[self _sendDelegateDidClickColumn:clickedColumn];
[self _changeSortDescriptorsForClickOnColumn:clickedColumn];
if (_allowsColumnSelection)
{
[self _noteSelectionIsChanging];
if (modifierFlags & CPPlatformActionKeyMask)
{
if ([self isColumnSelected:clickedColumn])
[self deselectColumn:clickedColumn];
else if ([self allowsMultipleSelection] == YES)
[self selectColumnIndexes:[CPIndexSet indexSetWithIndex:clickedColumn] byExtendingSelection:YES];
return;
}
else if (modifierFlags & CPShiftKeyMask)
{
// should be from clickedColumn to lastClickedColum with extending:(direction == previous selection)
var startColumn = MIN(clickedColumn, [_selectedColumnIndexes lastIndex]),
endColumn = MAX(clickedColumn, [_selectedColumnIndexes firstIndex]);
[self selectColumnIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(startColumn, endColumn - startColumn + 1)]
byExtendingSelection:YES];
return;
}
else
[self selectColumnIndexes:[CPIndexSet indexSetWithIndex:clickedColumn] byExtendingSelection:NO];
}
}
// From GNUSTEP
/*!
@ignore
*/
- (void)_changeSortDescriptorsForClickOnColumn:(int)column
{
var tableColumn = [_tableColumns objectAtIndex:column],
newMainSortDescriptor = [tableColumn sortDescriptorPrototype];
if (!newMainSortDescriptor)
return;
var oldMainSortDescriptor = nil,
oldSortDescriptors = [self sortDescriptors],
newSortDescriptors = [CPArray arrayWithArray:oldSortDescriptors],
e = [newSortDescriptors objectEnumerator],
descriptor = nil,
outdatedDescriptors = [CPArray array];
if ([_sortDescriptors count] > 0)
oldMainSortDescriptor = [[self sortDescriptors] objectAtIndex: 0];
// Remove every main descriptor equivalents (normally only one)
while ((descriptor = [e nextObject]) !== nil)
{
if ([[descriptor key] isEqual: [newMainSortDescriptor key]])
[outdatedDescriptors addObject:descriptor];
}
// Invert the sort direction when the same column header is clicked twice
if ([[newMainSortDescriptor key] isEqual:[oldMainSortDescriptor key]])
newMainSortDescriptor = [oldMainSortDescriptor reversedSortDescriptor];
[newSortDescriptors removeObjectsInArray:outdatedDescriptors];
[newSortDescriptors insertObject:newMainSortDescriptor atIndex:0];
[self setHighlightedTableColumn:tableColumn];
[self setSortDescriptors:newSortDescriptors];
}
/*!
Sets the indicator image of aTableColumn to anImage.
The tableview will set the sort indicator images automatically; if you want
a different image you can supply it here.
@param anImage the image for the column
@param aTableColumn the table column object for which to set the image
*/
- (void)setIndicatorImage:(CPImage)anImage inTableColumn:(CPTableColumn)aTableColumn
{
if (aTableColumn)
{
var headerView = [aTableColumn headerView];
if ([headerView respondsToSelector:@selector(_setIndicatorImage:)])
[headerView _setIndicatorImage:anImage];
}
}
/*!
@ignore
*/
- (CPImage)_tableHeaderSortImage
{
return [self currentValueForThemeAttribute:@"sort-image"];
}
/*!
@ignore
*/
- (CPImage)_tableHeaderReverseSortImage
{
return [self currentValueForThemeAttribute:@"sort-image-reversed"];
}
/*!
Returns the CPTableColumn object of the highlighted table column.
*/
- (CPTableColumn)highlightedTableColumn
{
return _currentHighlightedTableColumn;
}
/*!
Sets the table column for which the header should be highlighted.
*/
- (void)setHighlightedTableColumn:(CPTableColumn)aTableColumn
{
if (_currentHighlightedTableColumn == aTableColumn)
return;
if (_headerView)
{
if (_currentHighlightedTableColumn != nil)
[[_currentHighlightedTableColumn headerView] unsetThemeState:CPThemeStateSelected];
if (aTableColumn != nil)
[[aTableColumn headerView] setThemeState:CPThemeStateSelected];
}
_currentHighlightedTableColumn = aTableColumn;
}
/*!
Returns whether the receiver allows dragging the rows at rowIndexes with a drag initiated at mousedDownPoint.
@param rowIndexes an index set of rows to be dragged
@param aPoint the point at which the mouse was clicked.
*/
- (BOOL)canDragRowsWithIndexes:(CPIndexSet)rowIndexes atPoint:(CGPoint)mouseDownPoint
{
return [rowIndexes count] > 0 && [self numberOfRows] > 0;
}
/*!
Computes and returns an image to use for dragging. This method is invoked ONLY IF dragViewForRowsWithIndexes:tableColumns:event:offset: returns nil.
@param dragRows an index set with the dragged row indexes
@param theTableColumns an array of the table columns which are being dragged
@param dragEvent the event which initiated the drag
@param offset a point at which to set the drag image to be offset from the cursor
@return CPImage an image to use for the drag feedback
*/
- (CPImage)dragImageForRowsWithIndexes:(CPIndexSet)dragRows tableColumns:(CPArray)theTableColumns event:(CPEvent)dragEvent offset:(CGPoint)dragImageOffset
{
return [self valueForThemeAttribute:@"image-generic-file"];
}
/*!
Computes and returns a view to use for dragging. By default this is a slightly transparent copy of the dataviews which are being dragged.
You can override this in a subclass to show different dragging feedback. Additionally you can return nil from this method and implement:
- (CPImage)dragImageForRowsWithIndexes:tableColumns:event:offset: - if you want to return a simple image.
@param dragRows an index set with the dragged row indexes
@param theTableColumns an array of the table columns which are being dragged
@param dragEvent the event which initiated the drag
@param offset a point at which to set the drag image to be offset from the cursor
@return CPView a view used as the dragging feedback
*/
- (CPView)dragViewForRowsWithIndexes:(CPIndexSet)theDraggedRows tableColumns:(CPArray)theTableColumns event:(CPEvent)theDragEvent offset:(CGPoint)dragViewOffset
{
var bounds = [self bounds],
view = [[CPView alloc] initWithFrame:bounds];
[view setAlphaValue:0.7];
// We have to fetch all the data views for the selected rows and columns
// After that we can copy these add them to a transparent drag view and use that drag view
// to make it appear we are dragging images of those rows (as you would do in regular Cocoa)
var columnIndex = [theTableColumns count];
while (columnIndex--)
{
var tableColumn = [theTableColumns objectAtIndex:columnIndex],
row = [theDraggedRows firstIndex];
while (row !== CPNotFound)
{
var dataView = [self _newDataViewForRow:row tableColumn:tableColumn];
[dataView setFrame:[self frameOfDataViewAtColumn:columnIndex row:row]];
[self _setObjectValueForTableColumn:tableColumn row:row forView:dataView];
[view addSubview:dataView];
[_draggingViews addObject:dataView];
row = [theDraggedRows indexGreaterThanIndex:row];
}
}
var dragPoint = [self convertPoint:[theDragEvent locationInWindow] fromView:nil];
dragViewOffset.x = CGRectGetWidth(bounds) / 2 - dragPoint.x;
dragViewOffset.y = CGRectGetHeight(bounds) / 2 - dragPoint.y;
return view;
}
/*!
@ignore
// Fetches all the data views (from the datasource) for the column and it's visible rows
// Copy the dataviews add them to a transparent drag view and use that drag view
// to make it appear we are dragging images of those rows (as you would do in regular Cocoa)
*/
- (CPView)_dragViewForColumn:(int)theColumnIndex event:(CPEvent)theDragEvent offset:(CGPoint)theDragViewOffset
{
var dragView = [[_CPColumnDragView alloc] initWithLineColor:[self gridColor]],
tableColumn = [[self tableColumns] objectAtIndex:theColumnIndex],
defaultRowHeight = [self valueForThemeAttribute:@"default-row-height"],
bounds = CGRectMake(0.0, 0.0, [tableColumn width], CGRectGetHeight([self exposedRect]) + defaultRowHeight),
columnRect = [self rectOfColumn:theColumnIndex],
headerView = [tableColumn headerView],
row = [_exposedRows firstIndex];
[dragView setFrame:bounds];
while (row !== CPNotFound)
{
var dataView = [self _newDataViewForRow:row tableColumn:tableColumn],
dataViewFrame = [self frameOfDataViewAtColumn:theColumnIndex row:row];
// Only one column is ever dragged so we just place the view at
dataViewFrame.origin.x = 0.0;
// Offset by table header height - scroll position
dataViewFrame.origin.y = ( CGRectGetMinY(dataViewFrame) - CGRectGetMinY([self exposedRect]) ) + defaultRowHeight;
[dataView setFrame:dataViewFrame];
[self _setObjectValueForTableColumn:tableColumn row:row forView:dataView];
[dragView addSubview:dataView];
[_draggingViews addObject:dataView];
row = [_exposedRows indexGreaterThanIndex:row];
}
// Add a copy of the header view.
var columnHeaderView = [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:headerView]];
[dragView addSubview:columnHeaderView];
[dragView setBackgroundColor:[CPColor whiteColor]];
[dragView setAlphaValue:0.7];
return dragView;
}
/*!
Sets the default operation mask for the drag behavior of the table view.
@note isLocal is not implemented.
*/
- (void)setDraggingSourceOperationMask:(CPDragOperation)mask forLocal:(BOOL)isLocal
{
//ignore local for the time being since only one capp app can run at a time...
_dragOperationDefaultMask = mask;
}
/*!
This should be called inside tableView:validateDrop:... method
either CPTableViewDropOn or CPTableViewDropAbove,
Specify the row as -1 to select the whole table for drop on.
*/
- (void)setDropRow:(CPInteger)row dropOperation:(CPTableViewDropOperation)operation
{
if (row > [self numberOfRows] && operation === CPTableViewDropOn)
{
var numberOfRows = [self numberOfRows] + 1,
reason = @"Attempt to set dropRow=" + row +
" dropOperation=CPTableViewDropOn when [0 - " + numberOfRows + "] is valid range of rows.";
[[CPException exceptionWithName:@"Error" reason:reason userInfo:nil] raise];
}
_retargetedDropRow = row;
_retargetedDropOperation = operation;
}
/*!
Sets the feedback style for when the table is the destination of a drag operation.
This style is used to determine how the tableview looks when it is the receiver of a drag and drop operation.
Can be:
<pre>
CPTableViewDraggingDestinationFeedbackStyleNone
CPTableViewDraggingDestinationFeedbackStyleRegular
CPTableViewDraggingDestinationFeedbackStyleSourceList
</pre>
*/
- (void)setDraggingDestinationFeedbackStyle:(CPTableViewDraggingDestinationFeedbackStyle)aStyle
{
//FIX ME: this should vary up the highlight color, currently nothing is being done with it
_destinationDragStyle = aStyle;
}
/*!
Returns the tableview dragging destination feedback style.
Can be:
<pre>
CPTableViewDraggingDestinationFeedbackStyleNone
CPTableViewDraggingDestinationFeedbackStyleRegular
CPTableViewDraggingDestinationFeedbackStyleSourceList
</pre>
*/
- (CPTableViewDraggingDestinationFeedbackStyle)draggingDestinationFeedbackStyle
{
return _destinationDragStyle;
}
/*!
Sets whether vertical motion is treated as a drag or selection change to flag.
@param aFlag if flag is NO then vertical motion will not start a drag. The default is YES.
*/
- (void)setVerticalMotionCanBeginDrag:(BOOL)aFlag
{
_verticalMotionCanDrag = aFlag;
}
/*!
Returns YES if vertical motion can begin a drag of the tableview, otherwise NO.
*/
- (BOOL)verticalMotionCanBeginDrag
{
return _verticalMotionCanDrag;
}
- (CPTableColumn)_tableColumnForSortDescriptor:(CPSortDescriptor)theSortDescriptor
{
var tableColumns = [self tableColumns];
for (var i = 0; i < [tableColumns count]; i++)
{
var tableColumn = [tableColumns objectAtIndex:i],
sortDescriptorPrototype = [tableColumn sortDescriptorPrototype];
if (!sortDescriptorPrototype)
continue;
if ([sortDescriptorPrototype key] === [theSortDescriptor key]
&& [sortDescriptorPrototype selector] === [theSortDescriptor selector])
{
return tableColumn;
}
}
return nil;
}
/*!
Sets the table view's CPSortDescriptors objects in an array.
@param sortDescriptors an array of sort descriptors.
*/
- (void)setSortDescriptors:(CPArray)sortDescriptors
{
var oldSortDescriptors = [[self sortDescriptors] copy],
newSortDescriptors = [CPArray array];
if (sortDescriptors !== nil)
[newSortDescriptors addObjectsFromArray:sortDescriptors];
if ([newSortDescriptors isEqual:oldSortDescriptors])
return;
_sortDescriptors = newSortDescriptors;
var oldColumn = nil,
newColumn = nil;
if ([newSortDescriptors count] > 0)
{
var newMainSortDescriptor = [newSortDescriptors objectAtIndex:0];
newColumn = [self _tableColumnForSortDescriptor:newMainSortDescriptor];
}
if ([oldSortDescriptors count] > 0)
{
var oldMainSortDescriptor = [oldSortDescriptors objectAtIndex:0];
oldColumn = [self _tableColumnForSortDescriptor:oldMainSortDescriptor];
}
var image = [newMainSortDescriptor ascending] ? [self _tableHeaderSortImage] : [self _tableHeaderReverseSortImage];
[self setIndicatorImage:nil inTableColumn:oldColumn];
[self setIndicatorImage:image inTableColumn:newColumn];
[self _sendDataSourceSortDescriptorsDidChange:oldSortDescriptors];
var binderClass = [[self class] _binderClassForBinding:@"sortDescriptors"];
[[binderClass getBinding:@"sortDescriptors" forObject:self] reverseSetValueFor:@"sortDescriptors"];
}
/*!
Returns an array of the current sort descriptors currently used by the table.
*/
- (CPArray)sortDescriptors
{
return _sortDescriptors;
}
- (BOOL)_dataSourceRespondsToObjectValueForTableColumn
{
return _implementedDataSourceMethods & CPTableViewDataSource_tableView_objectValueForTableColumn_row_;
}
- (BOOL)_delegateRespondsToDataViewForTableColumn
{
return _implementedDelegateMethods & CPTableViewDelegate_tableView_dataViewForTableColumn_row_;
}
- (BOOL)_delegateRespondsToViewForTableColumn
{
return _implementedDelegateMethods & CPTableViewDelegate_tableView_viewForTableColumn_row_;
}
/*!
@ignore
*/
- (id)_objectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
{
var tableColumnUID = [aTableColumn UID],
tableColumnObjectValues = _objectValues[tableColumnUID];
if (!tableColumnObjectValues)
{
tableColumnObjectValues = [];
_objectValues[tableColumnUID] = tableColumnObjectValues;
}
var objectValue = tableColumnObjectValues[aRowIndex];
// tableView:objectValueForTableColumn:row: is optional if content bindings are in place.
if (objectValue === undefined)
{
if ([self _dataSourceRespondsToObjectValueForTableColumn])
{
objectValue = [_dataSource tableView:self objectValueForTableColumn:aTableColumn row:aRowIndex];
tableColumnObjectValues[aRowIndex] = objectValue;
}
else if (!_isViewBased && ![self infoForBinding:@"content"])
{
CPLog.warn(@"no content binding established and data source " + [_dataSource description] + " does not implement tableView:objectValueForTableColumn:row:");
}
}
return objectValue;
}
/*!
Returns a CGRect of the exposed area of the tableview.
*/
- (CGRect)exposedRect
{
if (!_exposedRect)
{
var superview = [self superview];
// FIXME: Should we be rect intersecting in case
// there are multiple views in the clip view?
if ([superview isKindOfClass:[CPClipView class]])
_exposedRect = [superview bounds];
else
_exposedRect = [self bounds];
}
return _exposedRect;
}
/*!
Loads all the data and dataviews for the receiver.
*/
- (void)load
{
if (_reloadAllRows)
{
[self _unloadDataViewsInRows:_exposedRows columns:_exposedColumns];
_exposedRows = [CPIndexSet indexSet];
_exposedColumns = [CPIndexSet indexSet];
_reloadAllRows = NO;
}
var exposedRect = [self exposedRect],
exposedRows = [CPIndexSet indexSetWithIndexesInRange:[self rowsInRect:exposedRect]],
exposedColumns = [self columnIndexesInRect:exposedRect],
obscuredRows = [_exposedRows copy],
obscuredColumns = [_exposedColumns copy];
[obscuredRows removeIndexes:exposedRows];
[obscuredColumns removeIndexes:exposedColumns];
var newlyExposedRows = [exposedRows copy],
newlyExposedColumns = [exposedColumns copy];
[newlyExposedRows removeIndexes:_exposedRows];
[newlyExposedColumns removeIndexes:_exposedColumns];
var previouslyExposedRows = [exposedRows copy],
previouslyExposedColumns = [exposedColumns copy];
[previouslyExposedRows removeIndexes:newlyExposedRows];
[previouslyExposedColumns removeIndexes:newlyExposedColumns];
[self _unloadDataViewsInRows:previouslyExposedRows columns:obscuredColumns];
[self _unloadDataViewsInRows:obscuredRows columns:previouslyExposedColumns];
[self _unloadDataViewsInRows:obscuredRows columns:obscuredColumns];
[self _unloadDataViewsInRows:newlyExposedRows columns:newlyExposedColumns];
[self _loadDataViewsInRows:previouslyExposedRows columns:newlyExposedColumns];
[self _loadDataViewsInRows:newlyExposedRows columns:previouslyExposedColumns];
[self _loadDataViewsInRows:newlyExposedRows columns:newlyExposedColumns];
_exposedRows = exposedRows;
_exposedColumns = exposedColumns;
[_tableDrawView setFrame:exposedRect];
[self setNeedsDisplay:YES];
// Now clear all the leftovers
// FIXME: this could be faster!
for (var identifier in _cachedDataViews)
{
var dataViews = _cachedDataViews[identifier],
count = dataViews.length;
while (count--)
[dataViews[count] removeFromSuperview];
}
// if we have any columns to remove do that here
if ([_differedColumnDataToRemove count])
{
for (var i = 0; i < _differedColumnDataToRemove.length; i++)
{
var data = _differedColumnDataToRemove[i],
column = data.column;
[column setHidden:data.shouldBeHidden];
[_tableColumns removeObject:column];
}
[_differedColumnDataToRemove removeAllObjects];
}
}
/*!
@ignore
*/
- (void)_unloadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
{
if (![rows count] || ![columns count])
return;
var rowArray = [],
columnArray = [];
[rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
[columns getIndexes:columnArray maxCount:-1 inIndexRange:nil];
var columnIndex = 0,
columnsCount = columnArray.length;
for (; columnIndex < columnsCount; ++columnIndex)
{
var column = columnArray[columnIndex],
tableColumn = _tableColumns[column],
tableColumnUID = [tableColumn UID],
rowIndex = 0,
rowsCount = rowArray.length;
for (; rowIndex < rowsCount; ++rowIndex)
{
var row = rowArray[rowIndex],
dataViews = _dataViewsForTableColumns[tableColumnUID];
if (!dataViews || row >= dataViews.length)
continue;
if (row === _editingRow && column === _editingColumn)
[[self window] makeFirstResponder:self];
var dataView = [dataViews objectAtIndex:row];
[dataViews replaceObjectAtIndex:row withObject:nil];
[self _enqueueReusableDataView:dataView];
}
}
}
/*!
@ignore
*/
- (void)_loadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
{
if (![rows count] || ![columns count])
return;
var rowArray = [],
rowRects = [],
columnArray = [];
[rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
[columns getIndexes:columnArray maxCount:-1 inIndexRange:nil];
UPDATE_COLUMN_RANGES_IF_NECESSARY();
var columnIndex = 0,
columnsCount = columnArray.length;
for (; columnIndex < columnsCount; ++columnIndex)
{
var column = columnArray[columnIndex],
tableColumn = _tableColumns[column];
if ([tableColumn isHidden] || tableColumn === _draggedColumn)
continue;
var tableColumnUID = [tableColumn UID];
if (!_dataViewsForTableColumns[tableColumnUID])
_dataViewsForTableColumns[tableColumnUID] = [];
var rowIndex = 0,
rowsCount = rowArray.length,
isColumnSelected = [_selectedColumnIndexes containsIndex:column];
for (; rowIndex < rowsCount; ++rowIndex)
{
var row = rowArray[rowIndex],
dataView = [self _newDataViewForRow:row tableColumn:tableColumn],
isButton = [dataView isKindOfClass:[CPButton class]],
isTextField = [dataView isKindOfClass:[CPTextField class]];
[dataView setFrame:[self frameOfDataViewAtColumn:column row:row]];
[self _setObjectValueForTableColumn:tableColumn row:row forView:dataView];
if ((_selectionHighlightStyle !== CPTableViewSelectionHighlightStyleNone) &&
(isColumnSelected || [self isRowSelected:row]))
{
[dataView setThemeState:CPThemeStateSelectedDataView];
}
else
[dataView unsetThemeState:CPThemeStateSelectedDataView];
// FIX ME: for performance reasons we might consider diverging from cocoa and moving this to the reloadData method
if (_implementedDelegateMethods & CPTableViewDelegate_tableView_isGroupRow_)
{
if ([_delegate tableView:self isGroupRow:row])
{
[_groupRows addIndex:row];
[dataView setThemeState:CPThemeStateGroupRow];
}
else
{
[_groupRows removeIndexesInRange:CPMakeRange(row, 1)];
[dataView unsetThemeState:CPThemeStateGroupRow];
}
[self setNeedsDisplay:YES];
}
if (_implementedDelegateMethods & CPTableViewDelegate_tableView_willDisplayView_forTableColumn_row_)
[_delegate tableView:self willDisplayView:dataView forTableColumn:tableColumn row:row];
if ([dataView superview] !== self)
[self addSubview:dataView];
_dataViewsForTableColumns[tableColumnUID][row] = dataView;
if (_isViewBased)
continue;
if (isButton || (_editingCellIndex && _editingCellIndex.x === column && _editingCellIndex.y === row))
{
if (isTextField)
{
[dataView setEditable:YES];
[dataView setSendsActionOnEndEditing:YES];
[dataView setSelectable:YES];
[dataView selectText:nil];
[dataView setBezeled:YES];
[dataView setDelegate:self];
}
[dataView setTarget:self];
[dataView setAction:@selector(_commitDataViewObjectValue:)];
dataView.tableViewEditedColumnObj = tableColumn;
dataView.tableViewEditedRowIndex = row;
}
else if (isTextField)
{
[dataView setEditable:NO];
[dataView setSelectable:NO];
}
}
}
}
- (void)_setObjectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow forView:(CPView)aDataView
{
if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_objectValueForTableColumn_row_)
[aDataView setObjectValue:[self _objectValueForTableColumn:aTableColumn row:aRow]];
// This gives the table column an opportunity to apply its bindings.
// It will override the value set above if there is a binding.
if (_contentBindingExpicitelySet)
[self _prepareContentBindedDataView:aDataView forRow:aRow];
else
// For both cell-based and view-based
[aTableColumn _prepareDataView:aDataView forRow:aRow];
}
- (void)_prepareContentBindedDataView:(CPView)dataView forRow:(CPInteger)aRow
{
var binder = [CPTableContentBinder getBinding:@"content" forObject:self],
content = [binder content],
rowContent = [content objectAtIndex:aRow];
[dataView setObjectValue:rowContent];
}
/*!
@ignore
*/
- (void)_layoutDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
{
var rowArray = [],
columnArray = [];
[rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
[columns getIndexes:columnArray maxCount:-1 inIndexRange:nil];
var columnIndex = 0,
columnsCount = columnArray.length;
for (; columnIndex < columnsCount; ++columnIndex)
{
var column = columnArray[columnIndex],
tableColumn = _tableColumns[column],
tableColumnUID = [tableColumn UID],
dataViewsForTableColumn = _dataViewsForTableColumns[tableColumnUID],
rowIndex = 0,
rowsCount = rowArray.length;
if (dataViewsForTableColumn)
{
for (; rowIndex < rowsCount; ++rowIndex)
{
var row = rowArray[rowIndex],
dataView = dataViewsForTableColumn[row];
[dataView setFrame:[self frameOfDataViewAtColumn:column row:row]];
}
}
}
}
/*!
@ignore
The action for any dataview that supports editing. This will only be called when the value was changed.
The table view becomes the first responder after user is done editing a dataview.
*/
- (void)_commitDataViewObjectValue:(id)sender
{
/*
makeFirstResponder at the end of this method causes the dataview to resign.
If the dataview resigning triggers the action (as CPTextField does), we come right
back here and start an infinite loop. So we have to check this flag first.
*/
if ([sender respondsToSelector:@selector(sendsActionOnEndEditing)] && [sender sendsActionOnEndEditing] && _editingCellIndex === nil)
return;
_editingCellIndex = nil;
if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_)
[_dataSource tableView:self setObjectValue:[sender objectValue] forTableColumn:sender.tableViewEditedColumnObj row:sender.tableViewEditedRowIndex];
// Allow the column binding to do a reverse set. Note that we do this even if the data source method above
// is implemented.
[sender.tableViewEditedColumnObj _reverseSetDataView:sender forRow:sender.tableViewEditedRowIndex];
if ([sender respondsToSelector:@selector(setEditable:)])
[sender setEditable:NO];
if ([sender respondsToSelector:@selector(setSelectable:)])
[sender setSelectable:NO];
if ([sender isKindOfClass:[CPTextField class]])
[sender setBezeled:NO];
[self reloadDataForRowIndexes:[CPIndexSet indexSetWithIndex:sender.tableViewEditedRowIndex]
columnIndexes:[CPIndexSet indexSetWithIndex:[_tableColumns indexOfObject:sender.tableViewEditedColumnObj]]];
[[self window] makeFirstResponder:self];
}
/*!
@ignore
Blur notification handler for editing textfields. This will always be called when a textfield loses focus.
This method is responsible for restoring the dataview to its non editable state.
*/
- (void)controlTextDidBlur:(CPNotification)theNotification
{
var dataView = [theNotification object];
if ([dataView respondsToSelector:@selector(setEditable:)])
[dataView setEditable:NO];
if ([dataView respondsToSelector:@selector(setSelectable:)])
[dataView setSelectable:NO];
if ([dataView isKindOfClass:[CPTextField class]])
[dataView setBezeled:NO];
_editingCellIndex = nil;
}
/*!
@ignore
*/
- (CPView)_viewForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
{
return [_delegate tableView:self viewForTableColumn:aTableColumn row:aRow];
}
- (CPView)_dataViewForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
{
return [_delegate tableView:self dataViewForTableColumn:aTableColumn row:aRow];
}
/*!
@ignore
*/
- (CPView)_newDataViewForRow:(CPInteger)aRow tableColumn:(CPTableColumn)aTableColumn
{
var view = nil;
if (_viewForTableColumnRowSelector)
view = objj_msgSend(self, _viewForTableColumnRowSelector, aTableColumn, aRow);
if (!view)
{
var columnIdentifier = [aTableColumn identifier];
// For Pre-Lion nibs, there is no automatic identifier for table column; use UID as identifier.
if (!columnIdentifier)
columnIdentifier = [aTableColumn UID];
view = [self makeViewWithIdentifier:columnIdentifier owner:_delegate];
if (!view)
view = [aTableColumn _newDataView];
[view setIdentifier:columnIdentifier];
}
return view;
}
/*
Returns a view with the specified identifier.
@param identifier The view identifier. Must not be nil.
@param owner The owner of the CIB that may be loaded and instituted to create a new view with the particular identifier.
@return A view for the row.
@discussion
Typically identifier is associated with an external CIB and the table view will automatically instantiate the CIB with the provided owner. The owner of the CIB that may be loaded and instantiated to create a new view with the particular identifier is typically the table view’s delegate. The owner is useful in setting up outlets and target and actions from the view.
This method will typically be called by the delegate in tableView:viewForTableColumn:row:, but it can also be overridden to provide custom views for the identifier. This method may also return a reused view with the same identifier that was no longer available on screen.
*/
- (id)makeViewWithIdentifier:(CPString)anIdentifier owner:(id)anOwner
{
if (!anIdentifier)
return nil;
var view,
// See if we have some reusable view available
reusableViews = _cachedDataViews[anIdentifier];
if (reusableViews && reusableViews.length)
view = reusableViews.pop();
// Otherwise see if we have a view in the cib with this identifier
else if (_isViewBased)
view = [self _unarchiveViewWithIdentifier:anIdentifier owner:anOwner];
return view;
}
/*!
@ignore
*/
- (CPView)_unarchiveViewWithIdentifier:(CPString)anIdentifier owner:(id)anOwner
{
var cib = [_archivedDataViews objectForKey:anIdentifier];
if (!cib && !_unavailable_custom_cibs[anIdentifier])
{
var bundle = anOwner ? [CPBundle bundleForClass:[anOwner class]] : [CPBundle mainBundle];
cib = [[CPCib alloc] initWithCibNamed:anIdentifier bundle:bundle];
}
if (!cib)
{
_unavailable_custom_cibs[anIdentifier] = YES;
return nil;
}
var objects = [],
load = [cib instantiateCibWithOwner:anOwner topLevelObjects:objects];
if (!load)
return nil;
var count = objects.length;
while (count--)
{
var obj = objects[count];
if ([obj isKindOfClass:[CPView class]])
{
[obj setIdentifier:anIdentifier];
[obj setAutoresizingMask:CPViewNotSizable];
return obj;
}
}
return nil;
}
- (void)_updateIsViewBased
{
if ([self _delegateRespondsToViewForTableColumn])
_viewForTableColumnRowSelector = @selector(_viewForTableColumn:row:);
else if ([self _delegateRespondsToDataViewForTableColumn])
_viewForTableColumnRowSelector = @selector(_dataViewForTableColumn:row:);
_isViewBased = (_viewForTableColumnRowSelector !== nil || _archivedDataViews !== nil);
}
/*!
@ignore
*/
- (void)_enqueueReusableDataView:(CPView)aDataView
{
if (!aDataView)
return;
// FIXME: yuck!
var identifier = [aDataView identifier];
if (!_cachedDataViews[identifier])
_cachedDataViews[identifier] = [aDataView];
else
_cachedDataViews[identifier].push(aDataView);
}
/*!