mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-08-25 13:07:02 +00:00
3752 lines
121 KiB
Plaintext
3752 lines
121 KiB
Plaintext
/*
|
|
* 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 <AppKit/CGGradient.j>
|
|
|
|
@import "CPControl.j"
|
|
@import "CPTableColumn.j"
|
|
@import "_CPCornerView.j"
|
|
@import "CPScroller.j"
|
|
|
|
CPTableViewColumnDidMoveNotification = @"CPTableViewColumnDidMoveNotification";
|
|
CPTableViewColumnDidResizeNotification = @"CPTableViewColumnDidResizeNotification";
|
|
CPTableViewSelectionDidChangeNotification = @"CPTableViewSelectionDidChangeNotification";
|
|
CPTableViewSelectionIsChangingNotification = @"CPTableViewSelectionIsChangingNotification";
|
|
|
|
#include "CoreGraphics/CGGeometry.h"
|
|
|
|
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_dataViewForTableColumn_row_ = 1 << 1,
|
|
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;
|
|
|
|
//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 set a dataSource which implements numberOfRowsInTableView:
|
|
and tableView:objectValueForTableColumn:row:
|
|
*/
|
|
@implementation CPTableView : CPControl
|
|
{
|
|
id _dataSource;
|
|
CPInteger _implementedDataSourceMethods;
|
|
|
|
id _delegate;
|
|
CPInteger _implementedDelegateMethods;
|
|
|
|
CPArray _tableColumns;
|
|
CPArray _tableColumnRanges;
|
|
CPInteger _dirtyTableColumnRangeIndex;
|
|
CPInteger _numberOfHiddenColumns;
|
|
|
|
BOOL _reloadAllRows;
|
|
Object _objectValues;
|
|
CPIndexSet _exposedRows;
|
|
CPIndexSet _exposedColumns;
|
|
|
|
Object _dataViewsForTableColumns;
|
|
Object _cachedDataViews;
|
|
|
|
//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;
|
|
CPTableColumn _currentHighlightedTableColumn;
|
|
unsigned _gridStyleMask;
|
|
|
|
unsigned _numberOfRows;
|
|
|
|
|
|
CPTableHeaderView _headerView;
|
|
_CPCornerView _cornerView;
|
|
|
|
CPIndexSet _selectedColumnIndexes;
|
|
CPIndexSet _selectedRowIndexes;
|
|
CPInteger _selectionAnchorRow;
|
|
CPInteger _lastSelectedRow;
|
|
CPIndexSet _previouslySelectedRowIndexes;
|
|
CGPoint _startTrackingPoint;
|
|
CPDate _startTrackingTimestamp;
|
|
BOOL _trackingPointMovedOutOfClickSlop;
|
|
CGPoint _editingCellIndex;
|
|
|
|
_CPTableDrawView _tableDrawView;
|
|
|
|
SEL _doubleAction;
|
|
CPInteger _clickedRow;
|
|
unsigned _columnAutoResizingStyle;
|
|
|
|
int _lastTrackedRowIndex;
|
|
CGPoint _originalMouseDownPoint;
|
|
BOOL _verticalMotionCanDrag;
|
|
unsigned _destinationDragStyle;
|
|
BOOL _isSelectingSession;
|
|
CPIndexSet _draggedRowIndexes;
|
|
|
|
_CPDropOperationDrawingView _dropOperationFeedbackView;
|
|
CPDragOperation _dragOperationDefaultMask;
|
|
int _retargetedDropRow;
|
|
CPDragOperation _retargetedDropOperation;
|
|
|
|
BOOL _disableAutomaticResizing @accessors(property=disableAutomaticResizing);
|
|
BOOL _lastColumnShouldSnap;
|
|
BOOL _implementsCustomDrawRow;
|
|
|
|
CPTableColumn _draggedColumn;
|
|
CPArray _differedColumnDataToRemove;
|
|
}
|
|
|
|
+ (CPString)themeClass
|
|
{
|
|
return @"tableview";
|
|
}
|
|
|
|
+ (id)themeAttributes
|
|
{
|
|
return [CPDictionary dictionaryWithObjects:[[CPNull null], [CPNull null], [CPNull null], [CPNull null], [CPNull null], [CPNull null], [CPNull null]]
|
|
forKeys:["alternating-row-colors", "grid-color", "highlighted-grid-color", "selection-color", "sourcelist-selection-color", "sort-image", "sort-image-reversed"]];
|
|
}
|
|
|
|
- (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 = 23.0;
|
|
|
|
[self setGridColor:[CPColor colorWithHexString:@"dce0e2"]];
|
|
[self setGridStyleMask:CPTableViewGridNone];
|
|
|
|
_headerView = [[CPTableHeaderView alloc] initWithFrame:CGRectMake(0, 0, [self bounds].size.width, _rowHeight)];
|
|
|
|
[_headerView setTableView:self];
|
|
|
|
_cornerView = nil; //[[_CPCornerView alloc] initWithFrame:CGRectMake(0, 0, [CPScroller scrollerWidth], CGRectGetHeight([_headerView frame]))];
|
|
|
|
_lastSelectedRow = -1;
|
|
_currentHighlightedTableColumn = nil;
|
|
|
|
_sortDescriptors = [CPArray array];
|
|
|
|
_draggedRowIndexes = [CPIndexSet indexSet];
|
|
_verticalMotionCanDrag = YES;
|
|
_isSelectingSession = NO;
|
|
_retargetedDropRow = nil;
|
|
_retargetedDropOperation = nil;
|
|
_dragOperationDefaultMask = nil;
|
|
_destinationDragStyle = CPTableViewDraggingDestinationFeedbackStyleRegular;
|
|
|
|
[self setBackgroundColor:[CPColor whiteColor]];
|
|
[self _init];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
// FIX ME: we have a lot of redundent init stuff in initWithFrame: and initWithCoder: we should move it all into here.
|
|
- (void)_init
|
|
{
|
|
_tableViewFlags = 0;
|
|
|
|
_selectedColumnIndexes = [CPIndexSet indexSet];
|
|
_selectedRowIndexes = [CPIndexSet indexSet];
|
|
|
|
_dropOperationFeedbackView = [[_CPDropOperationDrawingView alloc] initWithFrame:_CGRectMakeZero()];
|
|
[_dropOperationFeedbackView setTableView:self];
|
|
|
|
_lastColumnShouldSnap = NO;
|
|
|
|
if (!_alternatingRowBackgroundColors)
|
|
_alternatingRowBackgroundColors = [[CPColor whiteColor], [CPColor colorWithHexString:@"e4e7ff"]];
|
|
|
|
_selectionHighlightColor = [CPColor colorWithHexString:@"5f83b9"];
|
|
|
|
_tableColumnRanges = [];
|
|
_dirtyTableColumnRangeIndex = 0;
|
|
_numberOfHiddenColumns = 0;
|
|
|
|
_objectValues = { };
|
|
_dataViewsForTableColumns = { };
|
|
_dataViews= [];
|
|
_numberOfRows = 0;
|
|
_exposedRows = [CPIndexSet indexSet];
|
|
_exposedColumns = [CPIndexSet indexSet];
|
|
_cachedDataViews = { };
|
|
|
|
_tableDrawView = [[_CPTableDrawView alloc] initWithTableView:self];
|
|
[_tableDrawView setBackgroundColor:[CPColor clearColor]];
|
|
[self addSubview:_tableDrawView];
|
|
|
|
if (!_headerView)
|
|
_headerView = [[CPTableHeaderView alloc] initWithFrame:CGRectMake(0, 0, [self bounds].size.width, _rowHeight)];
|
|
|
|
[_headerView setTableView:self];
|
|
|
|
if (!_cornerView)
|
|
_cornerView = [[_CPCornerView alloc] initWithFrame:CGRectMake(0, 0, [CPScroller scrollerWidth], CGRectGetHeight([_headerView frame]))];
|
|
|
|
_draggedColumn = nil;
|
|
|
|
/* //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:)];
|
|
}
|
|
|
|
/*!
|
|
Sets the receiver's data source to a given object.
|
|
@param anObject The data source for the receiver. The object must implement the appropriate methods.
|
|
*/
|
|
- (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_;
|
|
else if (!hasContentBinding)
|
|
[CPException raise:CPInternalInconsistencyException
|
|
reason:[aDataSource description] + " does not implement numberOfRowsInTableView:."];
|
|
|
|
if ([_dataSource respondsToSelector:@selector(tableView:objectValueForTableColumn:row:)])
|
|
_implementedDataSourceMethods |= CPTableViewDataSource_tableView_objectValueForTableColumn_row_;
|
|
else if (!hasContentBinding)
|
|
[CPException raise:CPInternalInconsistencyException
|
|
reason:[aDataSource description] + " does not implement 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 = { };
|
|
|
|
// 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;
|
|
}
|
|
|
|
- (SEL)doubleAction
|
|
{
|
|
return _doubleAction;
|
|
}
|
|
|
|
/*
|
|
* - 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
|
|
|
|
- (void)setAllowsColumnReordering:(BOOL)shouldAllowColumnReordering
|
|
{
|
|
_allowsColumnReordering = !!shouldAllowColumnReordering;
|
|
}
|
|
|
|
- (BOOL)allowsColumnReordering
|
|
{
|
|
return _allowsColumnReordering;
|
|
}
|
|
|
|
- (void)setAllowsColumnResizing:(BOOL)shouldAllowColumnResizing
|
|
{
|
|
_allowsColumnResizing = !!shouldAllowColumnResizing;
|
|
}
|
|
|
|
- (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;
|
|
}
|
|
|
|
- (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;
|
|
}
|
|
|
|
- (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;
|
|
}
|
|
|
|
- (BOOL)allowsColumnSelection
|
|
{
|
|
return _allowsColumnSelection;
|
|
}
|
|
|
|
//Setting Display Attributes
|
|
|
|
- (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];
|
|
}
|
|
|
|
- (void)setThemeState:(int)astae
|
|
{
|
|
}
|
|
|
|
- (CGSize)intercellSpacing
|
|
{
|
|
return _CGSizeMakeCopy(_intercellSpacing);
|
|
}
|
|
|
|
- (void)setRowHeight:(unsigned)aRowHeight
|
|
{
|
|
aRowHeight = +aRowHeight;
|
|
|
|
if (_rowHeight === aRowHeight)
|
|
return;
|
|
|
|
_rowHeight = MAX(0.0, aRowHeight);
|
|
|
|
[self setNeedsLayout];
|
|
}
|
|
|
|
- (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;
|
|
}
|
|
|
|
- (BOOL)usesAlternatingRowBackgroundColors
|
|
{
|
|
return _usesAlternatingRowBackgroundColors;
|
|
}
|
|
|
|
/*!
|
|
Sets the colors for the rows as they alternate. The number of colors can be arbitrary. By deafult 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];
|
|
}
|
|
|
|
- (CPArray)alternatingRowBackgroundColors
|
|
{
|
|
return [self currentValueForThemeAttribute:@"alternating-row-colors"];
|
|
}
|
|
|
|
- (unsigned)selectionHighlightStyle
|
|
{
|
|
return _selectionHighlightStyle;
|
|
}
|
|
|
|
- (void)setSelectionHighlightStyle:(unsigned)aSelectionHighlightStyle
|
|
{
|
|
//early return for IE.
|
|
if (aSelectionHighlightStyle == CPTableViewSelectionHighlightStyleSourceList && !CPFeatureIsCompatible(CPHTMLCanvasFeature))
|
|
return;
|
|
|
|
_selectionHighlightStyle = aSelectionHighlightStyle;
|
|
[self setNeedsDisplay:YES];
|
|
|
|
if (aSelectionHighlightStyle === CPTableViewSelectionHighlightStyleSourceList)
|
|
_destinationDragStyle = CPTableViewDraggingDestinationFeedbackStyleSourceList;
|
|
else
|
|
_destinationDragStyle = CPTableViewDraggingDestinationFeedbackStyleRegular;
|
|
}
|
|
|
|
/*!
|
|
Sets the highlight color for a row or column selection
|
|
@param aColor a CPColor
|
|
*/
|
|
- (void)setSelectionHighlightColor:(CPColor)aColor
|
|
{
|
|
[self setValue:aColor forThemeAttribute:"selection-color"];
|
|
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
|
|
/*!
|
|
Returns the highlight color for a row or column selection.
|
|
*/
|
|
- (CPColor)selectionHighlightColor
|
|
{
|
|
return [self currentValueForThemeAttribute:@"selection-color"];
|
|
}
|
|
|
|
/*!
|
|
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:
|
|
CPSourceListGradient which is a CGGradient
|
|
CPSourceListTopLineColor which is a CPColor
|
|
CPSourceListBottomLineColor which is a CPColor
|
|
*/
|
|
- (void)setSelectionGradientColors:(CPDictionary)aDictionary
|
|
{
|
|
[self setValue:aDictionary forThemeAttribute:"sourcelist-selection-color"];
|
|
|
|
[self setNeedsDisplay:YES];
|
|
}
|
|
|
|
/*!
|
|
Returns a dictionary of containing the keys:
|
|
CPSourceListGradient
|
|
CPSourceListTopLineColor
|
|
CPSourceListBottomLineColor
|
|
*/
|
|
- (CPDictionary)selectionGradientColors
|
|
{
|
|
return [self currentValueForThemeAttribute:@"sourcelist-selection-color"];
|
|
}
|
|
|
|
/*!
|
|
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];
|
|
}
|
|
|
|
- (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];
|
|
}
|
|
|
|
- (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);
|
|
|
|
[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 differ 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];
|
|
}
|
|
|
|
- (void)_setDraggedColumn:(CPTableColumn)aColumn
|
|
{
|
|
if (_draggedColumn === aColumn)
|
|
return;
|
|
|
|
_draggedColumn = aColumn;
|
|
|
|
[self reloadDataForRowIndexes:_exposedRows columnIndexes:[CPIndexSet indexSetWithIndex:[_tableColumns indexOfObject:aColumn]]];
|
|
}
|
|
|
|
/*!
|
|
Moves the column and heading at a given index to a new given index.
|
|
@param columnIndex The current index of the column to move.
|
|
@param newIndex The new index for the moved column.
|
|
*/
|
|
- (void)moveColumn:(unsigned)fromIndex toColumn:(unsigned)toIndex
|
|
{
|
|
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];
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
*/
|
|
- (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]];
|
|
}
|
|
|
|
- (CPArray)tableColumns
|
|
{
|
|
return _tableColumns;
|
|
}
|
|
|
|
- (CPInteger)columnWithIdentifier:(CPString)anIdentifier
|
|
{
|
|
var index = 0,
|
|
count = NUMBER_OF_COLUMNS();
|
|
|
|
for (; index < count; ++index)
|
|
if ([_tableColumns[index] identifier] === anIdentifier)
|
|
return index;
|
|
|
|
return CPNotFound;
|
|
}
|
|
|
|
- (CPTableColumn)tableColumnWithIdentifier:(CPString)anIdentifier
|
|
{
|
|
var index = [self columnWithIdentifier:anIdentifier];
|
|
|
|
if (index === CPNotFound)
|
|
return nil;
|
|
|
|
return _tableColumns[index];
|
|
}
|
|
|
|
//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];
|
|
}
|
|
|
|
- (void)_setSelectedRowIndexes:(CPIndexSet)rows
|
|
{
|
|
var previousSelectedIndexes = [_selectedRowIndexes copy];
|
|
|
|
_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
|
|
|
|
[[CPKeyValueBinding 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];
|
|
}
|
|
|
|
- (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)];
|
|
|
|
for (var identifier in _dataViewsForTableColumns)
|
|
{
|
|
var dataViewsInTableColumn = _dataViewsForTableColumns[identifier];
|
|
|
|
var count = deselectRows.length;
|
|
while (count--)
|
|
[self _performSelection:NO forRow:deselectRows[count] context:dataViewsInTableColumn];
|
|
|
|
count = selectRows.length;
|
|
while (count--)
|
|
[self _performSelection:YES forRow:selectRows[count] context:dataViewsInTableColumn];
|
|
}
|
|
}
|
|
|
|
- (void)_performSelection:(BOOL)select forRow:(CPInteger)rowIndex context:(id)context
|
|
{
|
|
var view = context[rowIndex],
|
|
selector = select ? @"setThemeState:" : @"unsetThemeState:";
|
|
|
|
[view performSelector:CPSelectorFromString(selector) withObject:CPThemeStateSelectedDataView];
|
|
}
|
|
|
|
- (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 rowsCount = selectRows.length,
|
|
count = deselectColumns.length;
|
|
while (count--)
|
|
{
|
|
var columnIndex = deselectColumns[count],
|
|
identifier = [_tableColumns[columnIndex] UID],
|
|
dataViewsInTableColumn = _dataViewsForTableColumns[identifier];
|
|
|
|
for (var i = 0; i < rowsCount; i++)
|
|
{
|
|
var rowIndex = selectRows[i],
|
|
dataView = dataViewsInTableColumn[rowIndex];
|
|
[dataView unsetThemeState:CPThemeStateSelectedDataView];
|
|
}
|
|
|
|
if (_headerView)
|
|
{
|
|
var headerView = [_tableColumns[columnIndex] headerView];
|
|
[headerView unsetThemeState:CPThemeStateSelected];
|
|
}
|
|
}
|
|
|
|
count = selectColumns.length;
|
|
while (count--)
|
|
{
|
|
var columnIndex = selectColumns[count],
|
|
identifier = [_tableColumns[columnIndex] UID],
|
|
dataViewsInTableColumn = _dataViewsForTableColumns[identifier];
|
|
|
|
for (var i = 0; i < rowsCount; i++)
|
|
{
|
|
var rowIndex = selectRows[i],
|
|
dataView = dataViewsInTableColumn[rowIndex];
|
|
[dataView setThemeState:CPThemeStateSelectedDataView];
|
|
}
|
|
if (_headerView)
|
|
{
|
|
var headerView = [_tableColumns[columnIndex] headerView];
|
|
[headerView setThemeState:CPThemeStateSelected];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (int)selectedColumn
|
|
{
|
|
[_selectedColumnIndexes lastIndex];
|
|
}
|
|
|
|
- (CPIndexSet)selectedColumnIndexes
|
|
{
|
|
return _selectedColumnIndexes;
|
|
}
|
|
|
|
- (int)selectedRow
|
|
{
|
|
return _lastSelectedRow;
|
|
}
|
|
|
|
- (CPIndexSet)selectedRowIndexes
|
|
{
|
|
return [_selectedRowIndexes copy];
|
|
}
|
|
|
|
- (void)deselectColumn:(CPInteger)aColumn
|
|
{
|
|
var selectedColumnIndexes = [_selectedColumnIndexes copy];
|
|
[selectedColumnIndexes removeIndex:aColumn];
|
|
[self selectColumnIndexes:selectedColumnIndexes byExtendingSelection:NO];
|
|
[self _noteSelectionDidChange];
|
|
}
|
|
|
|
- (void)deselectRow:(CPInteger)aRow
|
|
{
|
|
var selectedRowIndexes = [_selectedRowIndexes copy];
|
|
[selectedRowIndexes removeIndex:aRow];
|
|
[self selectRowIndexes:selectedRowIndexes byExtendingSelection:NO];
|
|
[self _noteSelectionDidChange];
|
|
}
|
|
|
|
- (CPInteger)numberOfSelectedColumns
|
|
{
|
|
return [_selectedColumnIndexes count];
|
|
}
|
|
|
|
- (CPInteger)numberOfSelectedRows
|
|
{
|
|
return [_selectedRowIndexes count];
|
|
}
|
|
|
|
/*
|
|
- (CPInteger)selectedColumn
|
|
* - selectedRow
|
|
*/
|
|
|
|
- (BOOL)isColumnSelected:(CPInteger)aColumn
|
|
{
|
|
return [_selectedColumnIndexes containsIndex:aColumn];
|
|
}
|
|
|
|
- (BOOL)isRowSelected:(CPInteger)aRow
|
|
{
|
|
return [_selectedRowIndexes containsIndex:aRow];
|
|
}
|
|
/*
|
|
- (void)selectAll:
|
|
* - deselectAll:
|
|
* - allowsTypeSelect
|
|
* - setAllowsTypeSelect:
|
|
*/
|
|
|
|
/*!
|
|
Deselects all rows
|
|
*/
|
|
- (void)deselectAll
|
|
{
|
|
[self selectRowIndexes:[CPIndexSet indexSet] byExtendingSelection:NO];
|
|
[self selectColumnIndexes:[CPIndexSet indexSet] byExtendingSelection:NO];
|
|
}
|
|
|
|
//Table Dimensions
|
|
|
|
- (int)numberOfColumns
|
|
{
|
|
return NUMBER_OF_COLUMNS();
|
|
}
|
|
|
|
/*
|
|
Returns the number of rows in the receiver.
|
|
*/
|
|
- (int)numberOfRows
|
|
{
|
|
if (_numberOfRows)
|
|
return _numberOfRows;
|
|
|
|
var contentBindingInfo = [self infoForBinding:@"content"];
|
|
|
|
if (contentBindingInfo)
|
|
{
|
|
var destination = [contentBindingInfo objectForKey:CPObservedObjectKey],
|
|
keyPath = [contentBindingInfo objectForKey:CPObservedKeyPathKey];
|
|
|
|
return [[destination valueForKeyPath:keyPath] count];
|
|
}
|
|
|
|
else if (_dataSource)
|
|
return [_dataSource numberOfRowsInTableView:self];
|
|
|
|
return 0;
|
|
}
|
|
|
|
//Displaying Cell
|
|
/*
|
|
* - preparedCellAtColumn:row:
|
|
*/
|
|
|
|
//Editing Cells
|
|
|
|
/*!
|
|
Edits the indicated row.
|
|
*/
|
|
- (void)editColumn:(CPInteger)columnIndex row:(CPInteger)rowIndex withEvent:(CPEvent)theEvent select:(BOOL)flag
|
|
{
|
|
if (![self isRowSelected:rowIndex])
|
|
[[CPException exceptionWithName:@"Error" reason:@"Attempt to edit row="+rowIndex+" when not selected." userInfo:nil] raise];
|
|
|
|
// TODO Do something with flag.
|
|
|
|
_editingCellIndex = CGPointMake(columnIndex, rowIndex);
|
|
[self reloadDataForRowIndexes:[CPIndexSet indexSetWithIndex:rowIndex]
|
|
columnIndexes:[CPIndexSet indexSetWithIndex:columnIndex]];
|
|
}
|
|
|
|
/*!
|
|
Returns the column of the currently edited cell, or -1 if none.
|
|
*/
|
|
- (CPInteger)editedColumn
|
|
{
|
|
if (!_editingCellIndex)
|
|
return -1;
|
|
return _editingCellIndex.x;
|
|
}
|
|
|
|
/*!
|
|
Returns the row of the currently edited cell, or -1 if none.
|
|
*/
|
|
- (CPInteger)editedRow
|
|
{
|
|
if (!_editingCellIndex)
|
|
return -1;
|
|
return _editingCellIndex.x;
|
|
}
|
|
|
|
//Setting Auxiliary Views
|
|
/*
|
|
* - setHeaderView:
|
|
* - headerView
|
|
* - setCornerView:
|
|
* - cornerView
|
|
*/
|
|
|
|
- (CPView)cornerView
|
|
{
|
|
return _cornerView;
|
|
}
|
|
|
|
- (void)setCornerView:(CPView)aView
|
|
{
|
|
if (_cornerView === aView)
|
|
return;
|
|
|
|
_cornerView = aView;
|
|
|
|
var scrollView = [[self superview] superview];
|
|
|
|
if ([scrollView isKindOfClass:[CPScrollView class]] && [scrollView documentView] === self)
|
|
[scrollView _updateCornerAndHeaderView];
|
|
}
|
|
|
|
- (CPView)headerView
|
|
{
|
|
return _headerView;
|
|
}
|
|
|
|
- (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]))];
|
|
}
|
|
|
|
var scrollView = [[self superview] superview];
|
|
|
|
if ([scrollView isKindOfClass:[CPScrollView class]] && [scrollView documentView] === self)
|
|
[scrollView _updateCornerAndHeaderView];
|
|
}
|
|
|
|
//Layout Support
|
|
|
|
// Complexity:
|
|
// O(Columns)
|
|
- (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)
|
|
- (CGRect)rectOfColumn:(CPInteger)aColumnIndex
|
|
{
|
|
aColumnIndex = +aColumnIndex;
|
|
|
|
var column = [[self tableColumns] objectAtIndex:aColumnIndex];
|
|
|
|
if ([column isHidden] || aColumnIndex < 0 || aColumnIndex >= NUMBER_OF_COLUMNS())
|
|
return _CGRectMakeZero();
|
|
|
|
UPDATE_COLUMN_RANGES_IF_NECESSARY();
|
|
|
|
var range = _tableColumnRanges[aColumnIndex];
|
|
|
|
return _CGRectMake(range.location, 0.0, range.length, CGRectGetHeight([self bounds]));
|
|
}
|
|
|
|
- (CGRect)rectOfRow:(CPInteger)aRowIndex
|
|
{
|
|
var height = _rowHeight + _intercellSpacing.height;
|
|
|
|
return _CGRectMake(0.0, aRowIndex * height, _CGRectGetWidth([self bounds]), height);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// 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
|
|
- (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;
|
|
}
|
|
|
|
- (CPInteger)rowAtPoint:(CGPoint)aPoint
|
|
{
|
|
var y = aPoint.y,
|
|
row = FLOOR(y / (_rowHeight + _intercellSpacing.height));
|
|
|
|
if (row >= _numberOfRows)
|
|
return -1;
|
|
|
|
return row;
|
|
}
|
|
|
|
- (CGRect)frameOfDataViewAtColumn:(CPInteger)aColumn row:(CPInteger)aRow
|
|
{
|
|
UPDATE_COLUMN_RANGES_IF_NECESSARY();
|
|
|
|
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);
|
|
}
|
|
|
|
- (void)resizeWithOldSuperviewSize:(CGSize)aSize
|
|
{
|
|
[super resizeWithOldSuperviewSize:aSize];
|
|
|
|
if (_disableAutomaticResizing)
|
|
return;
|
|
|
|
var mask = _columnAutoResizingStyle;
|
|
|
|
if (mask === CPTableViewUniformColumnAutoresizingStyle)
|
|
[self _resizeAllColumnUniformlyWithOldSize:aSize];
|
|
else if (mask === CPTableViewLastColumnOnlyAutoresizingStyle)
|
|
[self sizeLastColumnToFit];
|
|
else if (mask === CPTableViewFirstColumnOnlyAutoresizingStyle)
|
|
[self _autoResizeFirstColumn];
|
|
}
|
|
|
|
- (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];
|
|
newWidth = MAX([columnToResize minWidth], newWidth);
|
|
newWidth = MIN([columnToResize maxWidth], newWidth);
|
|
|
|
[columnToResize setWidth:FLOOR(newWidth)];
|
|
}
|
|
|
|
[self setNeedsLayout];
|
|
}
|
|
|
|
- (void)_resizeAllColumnUniformlyWithOldSize:(CGSize)oldSize
|
|
{
|
|
var superview = [self superview];
|
|
|
|
if (!superview)
|
|
return;
|
|
|
|
var superviewSize = [superview bounds].size;
|
|
|
|
UPDATE_COLUMN_RANGES_IF_NECESSARY();
|
|
|
|
var count = NUMBER_OF_COLUMNS(),
|
|
visColumns = [[CPArray alloc] init],
|
|
buffer = 0.0;
|
|
|
|
// Fixme: cache resizable columns because they won't changes betwwen two calls to this method.
|
|
for (var i=0; i < count; i++)
|
|
{
|
|
var tableColumn = _tableColumns[i];
|
|
if(![tableColumn isHidden] && ([tableColumn resizingMask] & CPTableColumnAutoresizingMask))
|
|
[visColumns addObject:i];
|
|
}
|
|
|
|
// redefine count
|
|
count = [visColumns count];
|
|
|
|
//if there are columns
|
|
if (count > 0)
|
|
{
|
|
var maxXofColumns = CGRectGetMaxX([self rectOfColumn:visColumns[count - 1]]);
|
|
|
|
// If the x value of the end of the last column is between the current bounds and the previous bounds we should snap.
|
|
if (!_lastColumnShouldSnap && (maxXofColumns >= superviewSize.width && maxXofColumns <= oldSize.width || maxXofColumns <= superviewSize.width && maxXofColumns >= oldSize.width))
|
|
{
|
|
//set the snap mask
|
|
_lastColumnShouldSnap = YES;
|
|
//then we need to make sure everything is set correctly.
|
|
[self _resizeAllColumnUniformlyWithOldSize:CGSizeMake(maxXofColumns, 0)];
|
|
}
|
|
|
|
if (!_lastColumnShouldSnap)
|
|
return;
|
|
|
|
// FIX ME: This is wrong because this should continue to resize all columns
|
|
// If the last column reaches it's max/min it will simply stop resizing,
|
|
// correct behavior is to resize all columns until they reach their min/max
|
|
|
|
for (var i = 0; i < count; i++)
|
|
{
|
|
var column = visColumns[i],
|
|
columnToResize = _tableColumns[column],
|
|
currentBuffer = buffer / (count - i),
|
|
realNewWidth = ([columnToResize width] / oldSize.width * [superview bounds].size.width) + currentBuffer,
|
|
newWidth = realNewWidth;
|
|
newWidth = MAX([columnToResize minWidth], newWidth);
|
|
newWidth = MIN([columnToResize maxWidth], newWidth);
|
|
buffer -= currentBuffer;
|
|
|
|
// the buffer takes into account the min/max width of the column
|
|
buffer += realNewWidth - newWidth;
|
|
|
|
[columnToResize setWidth:newWidth];
|
|
}
|
|
|
|
// if there is space left over that means column resize was too long or too short
|
|
if (buffer !== 0)
|
|
_lastColumnShouldSnap = NO;
|
|
}
|
|
|
|
[self setNeedsLayout];
|
|
}
|
|
|
|
/*!
|
|
Sets the column autoresizing style of the receiver to a given style.
|
|
@param aStyle The column autoresizing style for the receiver.
|
|
CPTableViewNoColumnAutoresizing, CPTableViewUniformColumnAutoresizingStyle,
|
|
CPTableViewLastColumnOnlyAutoresizingStyle, CPTableViewFirstColumnOnlyAutoresizingStyle
|
|
*/
|
|
- (void)setColumnAutoresizingStyle:(unsigned)style
|
|
{
|
|
//FIX ME: CPTableViewSequentialColumnAutoresizingStyle and CPTableViewReverseSequentialColumnAutoresizingStyle are not yet implemented
|
|
_columnAutoResizingStyle = style;
|
|
}
|
|
|
|
- (unsigned)columnAutoresizingStyle
|
|
{
|
|
return _columnAutoResizingStyle;
|
|
}
|
|
|
|
/*!
|
|
Resizes the last column if there�s room so the receiver fits exactly within its enclosing clip view.
|
|
*/
|
|
- (void)sizeLastColumnToFit
|
|
{
|
|
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 row that's not hidden
|
|
while (count-- && [_tableColumns[count] isHidden]) ;
|
|
|
|
//if the last row exists
|
|
if (count >= 0)
|
|
{
|
|
var columnToResize = _tableColumns[count],
|
|
newSize = MAX(0.0, superviewSize.width - CGRectGetMinX([self rectOfColumn:count]) - _intercellSpacing.width);
|
|
|
|
if (newSize > 0)
|
|
{
|
|
newSize = MAX([columnToResize minWidth], newSize);
|
|
newSize = MIN([columnToResize maxWidth], newSize);
|
|
[columnToResize setWidth:newSize];
|
|
}
|
|
}
|
|
|
|
[self setNeedsLayout];
|
|
}
|
|
|
|
- (void)noteNumberOfRowsChanged
|
|
{
|
|
var oldNumberOfRows = _numberOfRows;
|
|
|
|
_numberOfRows = nil;
|
|
_numberOfRows = [self numberOfRows];
|
|
|
|
// remove row indexes from the selection if they no longer exist
|
|
var hangingSelections = oldNumberOfRows - _numberOfRows;
|
|
|
|
if (hangingSelections > 0)
|
|
{
|
|
[_selectedRowIndexes removeIndexesInRange:CPMakeRange(_numberOfRows, hangingSelections)];
|
|
[self _noteSelectionDidChange];
|
|
}
|
|
|
|
[self tile];
|
|
}
|
|
|
|
- (void)tile
|
|
{
|
|
UPDATE_COLUMN_RANGES_IF_NECESSARY();
|
|
|
|
// FIXME: variable row heights.
|
|
var width = _tableColumnRanges.length > 0 ? CPMaxRange([_tableColumnRanges lastObject]) : 0.0,
|
|
height = (_rowHeight + _intercellSpacing.height) * _numberOfRows,
|
|
superview = [self superview];
|
|
|
|
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];
|
|
}
|
|
|
|
/*
|
|
* - tile
|
|
* - sizeToFit
|
|
* - noteHeightOfRowsWithIndexesChanged:
|
|
*/
|
|
//Scrolling
|
|
/*
|
|
* - scrollRowToVisible:
|
|
* - scrollColumnToVisible:
|
|
*/
|
|
|
|
/*!
|
|
Scrolls the receiver vertically in an enclosing NSClipView so the row specified by rowIndex is visible.
|
|
@param aRowIndex the index of the row to scroll to.
|
|
*/
|
|
- (void)scrollRowToVisible:(int)rowIndex
|
|
{
|
|
[self scrollRectToVisible:[self rectOfRow:rowIndex]];
|
|
}
|
|
|
|
/*!
|
|
Scrolls the receiver and header view horizontally in an enclosing NSClipView so the column specified by columnIndex is visible.
|
|
@param aColumnIndex the index of the column to scroll to.
|
|
*/
|
|
- (void)scrollColumnToVisible:(int)columnIndex
|
|
{
|
|
[self scrollRectToVisible:[self rectOfColumn:columnIndex]];
|
|
/*FIX ME: tableview header isn't rendered until you click the horizontal scroller (or scroll)*/
|
|
}
|
|
|
|
//Persistence
|
|
/*
|
|
* - autosaveName
|
|
* - autosaveTableColumns
|
|
* - setAutosaveName:
|
|
* - setAutosaveTableColumns:
|
|
*/
|
|
|
|
//Setting the Delegate:(id)aDelegate
|
|
|
|
- (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:dataViewForTableColumn:row:)])
|
|
_implementedDelegateMethods |= CPTableViewDelegate_tableView_dataViewForTableColumn_row_;
|
|
|
|
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(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];
|
|
}
|
|
|
|
- (id)delegate
|
|
{
|
|
return _delegate;
|
|
}
|
|
|
|
- (void)_sendDelegateDidClickColumn:(int)column
|
|
{
|
|
if (_implementedDelegateMethods & CPTableViewDelegate_tableView_didClickTableColumn_)
|
|
[_delegate tableView:self didClickTableColumn:_tableColumns[column]];
|
|
}
|
|
|
|
- (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]];
|
|
}
|
|
|
|
- (void)_sendDataSourceSortDescriptorsDidChange:(CPArray)oldDescriptors
|
|
{
|
|
if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_sortDescriptorsDidChange_)
|
|
[_dataSource tableView:self sortDescriptorsDidChange:oldDescriptors];
|
|
}
|
|
|
|
- (void)_didClickTableColumn:(int)clickedColumn modifierFlags:(unsigned)modifierFlags
|
|
{
|
|
[self _sendDelegateDidClickColumn:clickedColumn];
|
|
|
|
if (_allowsColumnSelection)
|
|
{
|
|
[self _noteSelectionIsChanging];
|
|
if (modifierFlags & CPCommandKeyMask)
|
|
{
|
|
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];
|
|
}
|
|
|
|
[self _changeSortDescriptorsForClickOnColumn:clickedColumn];
|
|
}
|
|
|
|
// From GNUSTEP
|
|
- (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];
|
|
|
|
// Update indicator image & highlighted column before
|
|
var image = [newMainSortDescriptor ascending] ? [self _tableHeaderSortImage] : [self _tableHeaderReverseSortImage];
|
|
|
|
[self setIndicatorImage:nil inTableColumn:_currentHighlightedTableColumn];
|
|
[self setIndicatorImage:image inTableColumn:tableColumn];
|
|
[self setHighlightedTableColumn:tableColumn];
|
|
|
|
[self setSortDescriptors:newSortDescriptors];
|
|
}
|
|
|
|
- (void)setIndicatorImage:(CPImage)anImage inTableColumn:(CPTableColumn)aTableColumn
|
|
{
|
|
if (aTableColumn)
|
|
{
|
|
var headerView = [aTableColumn headerView];
|
|
if ([headerView respondsToSelector:@selector(_setIndicatorImage:)])
|
|
[headerView _setIndicatorImage:anImage];
|
|
}
|
|
}
|
|
|
|
- (CPImage)_tableHeaderSortImage
|
|
{
|
|
return [self currentValueForThemeAttribute:"sort-image"];
|
|
}
|
|
|
|
- (CPImage)_tableHeaderReverseSortImage
|
|
{
|
|
return [self currentValueForThemeAttribute:"sort-image-reversed"];
|
|
}
|
|
|
|
//Highlightable Column Headers
|
|
|
|
- (CPTableColumn)highlightedTableColumn
|
|
{
|
|
return _currentHighlightedTableColumn;
|
|
}
|
|
|
|
- (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 YES;
|
|
}
|
|
|
|
- (CPImage)dragImageForRowsWithIndexes:(CPIndexSet)dragRows tableColumns:(CPArray)theTableColumns event:(CPEvent)dragEvent offset:(CPPointPointer)dragImageOffset
|
|
{
|
|
return [[CPImage alloc] initWithContentsOfFile:@"Frameworks/AppKit/Resources/GenericFile.png" size:CGSizeMake(32,32)];
|
|
}
|
|
|
|
- (CPView)dragViewForRowsWithIndexes:(CPIndexSet)theDraggedRows tableColumns:(CPArray)theTableColumns event:(CPEvent)theDragEvent offset:(CPPointPointer)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]];
|
|
[dataView setObjectValue:[self _objectValueForTableColumn:tableColumn row:row]];
|
|
|
|
// If the column uses content bindings, allow them to override the objectValueForTableColumn.
|
|
[tableColumn prepareDataView:dataView forRow:row];
|
|
|
|
[view addSubview: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:(CPPointPointer)theDragViewOffset
|
|
{
|
|
var dragView = [[_CPColumnDragView alloc] initWithLineColor:[self gridColor]];
|
|
tableColumn = [[self tableColumns] objectAtIndex:theColumnIndex],
|
|
bounds = CPRectMake(0.0, 0.0, [tableColumn width], _CGRectGetHeight([self visibleRect]) + 23.0),
|
|
columnRect = [self rectOfColumn:theColumnIndex],
|
|
headerView = [tableColumn headerView],
|
|
row = [_exposedRows firstIndex];
|
|
|
|
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 visibleRect]) ) + 23.0;
|
|
[dataView setFrame:dataViewFrame];
|
|
|
|
[dataView setObjectValue:[self _objectValueForTableColumn:tableColumn row:row]];
|
|
[dragView addSubview:dataView];
|
|
|
|
row = [_exposedRows indexGreaterThanIndex:row];
|
|
}
|
|
|
|
// Add the column header view
|
|
var headerFrame = [headerView frame];
|
|
headerFrame.origin = _CGPointMakeZero();
|
|
|
|
var columnHeaderView = [[_CPTableColumnHeaderView alloc] initWithFrame:headerFrame];
|
|
[columnHeaderView setStringValue:[headerView stringValue]];
|
|
[columnHeaderView setThemeState:[headerView themeState]];
|
|
[dragView addSubview:columnHeaderView];
|
|
|
|
[dragView setBackgroundColor:[CPColor whiteColor]];
|
|
[dragView setAlphaValue:0.7];
|
|
[dragView setFrame:bounds];
|
|
|
|
return dragView;
|
|
}
|
|
|
|
- (void)setDraggingSourceOperationMask:(CPDragOperation)mask forLocal:(BOOL)isLocal
|
|
{
|
|
//ignoral 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 drop on or above,
|
|
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;
|
|
var 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
|
|
Can be:
|
|
None
|
|
Regular
|
|
Source List
|
|
*/
|
|
- (void)setDraggingDestinationFeedbackStyle:(CPTableViewDraggingDestinationFeedbackStyle)aStyle
|
|
{
|
|
//FIX ME: this should vary up the highlight color, currently nothing is being done with it
|
|
_destinationDragStyle = aStyle;
|
|
}
|
|
|
|
- (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;
|
|
}
|
|
|
|
- (BOOL)verticalMotionCanBeginDrag
|
|
{
|
|
return _verticalMotionCanDrag;
|
|
}
|
|
|
|
|
|
- (void)setSortDescriptors:(CPArray)sortDescriptors
|
|
{
|
|
var oldSortDescriptors = [self sortDescriptors],
|
|
newSortDescriptors = nil;
|
|
|
|
if (sortDescriptors == nil)
|
|
newSortDescriptors = [CPArray array];
|
|
else
|
|
newSortDescriptors = [CPArray arrayWithArray:sortDescriptors];
|
|
|
|
if ([newSortDescriptors isEqual:oldSortDescriptors])
|
|
return;
|
|
|
|
_sortDescriptors = newSortDescriptors;
|
|
|
|
[self _sendDataSourceSortDescriptorsDidChange:oldSortDescriptors];
|
|
}
|
|
|
|
- (CPArray)sortDescriptors
|
|
{
|
|
return _sortDescriptors;
|
|
}
|
|
|
|
//Text Delegate Methods
|
|
/*
|
|
* - textShouldBeginEditing:
|
|
* - textDidBeginEditing:
|
|
* - textDidChange:
|
|
* - textShouldEndEditing:
|
|
* - textDidEndEditing:
|
|
*/
|
|
|
|
- (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 && (_implementedDataSourceMethods & CPTableViewDataSource_tableView_objectValueForTableColumn_row_))
|
|
{
|
|
objectValue = [_dataSource tableView:self objectValueForTableColumn:aTableColumn row:aRowIndex];
|
|
tableColumnObjectValues[aRowIndex] = objectValue;
|
|
}
|
|
|
|
return objectValue;
|
|
}
|
|
|
|
- (void)load
|
|
{
|
|
if (_reloadAllRows)
|
|
{
|
|
[self _unloadDataViewsInRows:_exposedRows columns:_exposedColumns];
|
|
|
|
_exposedRows = [CPIndexSet indexSet];
|
|
_exposedColumns = [CPIndexSet indexSet];
|
|
|
|
_reloadAllRows = NO;
|
|
}
|
|
|
|
var exposedRect = [self visibleRect],
|
|
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];
|
|
}
|
|
|
|
}
|
|
|
|
- (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];
|
|
|
|
var rowIndex = 0,
|
|
rowsCount = rowArray.length;
|
|
|
|
for (; rowIndex < rowsCount; ++rowIndex)
|
|
{
|
|
var row = rowArray[rowIndex],
|
|
dataViews = _dataViewsForTableColumns[tableColumnUID];
|
|
|
|
if (!dataViews || row >= dataViews.length)
|
|
continue;
|
|
|
|
var dataView = [dataViews objectAtIndex:row];
|
|
|
|
[dataViews replaceObjectAtIndex:row withObject:nil];
|
|
|
|
[self _enqueueReusableDataView:dataView];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (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;
|
|
|
|
var 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]];
|
|
[dataView setObjectValue:[self _objectValueForTableColumn:tableColumn row:row]];
|
|
|
|
//This gives the table column an opportunity to apply the bindings.
|
|
//It will override the value set in the data source, if there is a data source.
|
|
//It will do nothing if there is no value binding set.
|
|
[tableColumn prepareDataView:dataView forRow:row];
|
|
|
|
if (isColumnSelected || [self isRowSelected:row])
|
|
[dataView setThemeState:CPThemeStateSelectedDataView];
|
|
else
|
|
[dataView unsetThemeState:CPThemeStateSelectedDataView];
|
|
|
|
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 (isButton || (_editingCellIndex && _editingCellIndex.x === column && _editingCellIndex.y === row))
|
|
{
|
|
if (!isButton)
|
|
_editingCellIndex = undefined;
|
|
|
|
if (isTextField)
|
|
{
|
|
[dataView setEditable:YES];
|
|
[dataView setSendsActionOnEndEditing:YES];
|
|
[dataView setSelectable:YES];
|
|
[dataView selectText:nil]; // Doesn't seem to actually work (yet?).
|
|
}
|
|
|
|
[dataView setTarget:self];
|
|
[dataView setAction:@selector(_commitDataViewObjectValue:)];
|
|
dataView.tableViewEditedColumnObj = tableColumn;
|
|
dataView.tableViewEditedRowIndex = row;
|
|
}
|
|
else if (isTextField)
|
|
{
|
|
[dataView setEditable:NO];
|
|
[dataView setSelectable:NO];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (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],
|
|
columnRange = _tableColumnRanges[column];
|
|
|
|
var rowIndex = 0,
|
|
rowsCount = rowArray.length;
|
|
|
|
for (; rowIndex < rowsCount; ++rowIndex)
|
|
{
|
|
var row = rowArray[rowIndex],
|
|
dataView = dataViewsForTableColumn[row];
|
|
|
|
[dataView setFrame:[self frameOfDataViewAtColumn:column row:row]];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)_commitDataViewObjectValue:(id)sender
|
|
{
|
|
[_dataSource tableView:self setObjectValue:[sender objectValue] forTableColumn:sender.tableViewEditedColumnObj row:sender.tableViewEditedRowIndex];
|
|
|
|
if ([sender respondsToSelector:@selector(setEditable:)])
|
|
[sender setEditable:NO];
|
|
}
|
|
|
|
- (CPView)_newDataViewForRow:(CPInteger)aRow tableColumn:(CPTableColumn)aTableColumn
|
|
{
|
|
if ((_implementedDelegateMethods & CPTableViewDelegate_tableView_dataViewForTableColumn_row_))
|
|
{
|
|
var dataView = [_delegate tableView:self dataViewForTableColumn:aTableColumn row:aRow];
|
|
[aTableColumn setDataView:dataView];
|
|
}
|
|
|
|
|
|
return [aTableColumn _newDataViewForRow:aRow];
|
|
}
|
|
|
|
- (void)_enqueueReusableDataView:(CPView)aDataView
|
|
{
|
|
if (!aDataView)
|
|
return;
|
|
|
|
// FIXME: yuck!
|
|
var identifier = aDataView.identifier;
|
|
|
|
if (!_cachedDataViews[identifier])
|
|
_cachedDataViews[identifier] = [aDataView];
|
|
else
|
|
_cachedDataViews[identifier].push(aDataView);
|
|
}
|
|
|
|
- (void)setFrameSize:(CGSize)aSize
|
|
{
|
|
[super setFrameSize:aSize];
|
|
|
|
if (_headerView)
|
|
[_headerView setFrameSize:_CGSizeMake(_CGRectGetWidth([self frame]), _CGRectGetHeight([_headerView frame]))];
|
|
}
|
|
|
|
- (void)setNeedsDisplay:(BOOL)aFlag
|
|
{
|
|
[super setNeedsDisplay:aFlag];
|
|
[_tableDrawView setNeedsDisplay:aFlag];
|
|
}
|
|
|
|
- (void)_drawRect:(CGRect)aRect
|
|
{
|
|
// FIX ME: All three of these methods will likely need to be rewritten for 1.0
|
|
// We've got grid drawing in highlightSelection and crap everywhere.
|
|
|
|
var exposedRect = [self visibleRect];
|
|
|
|
[self drawBackgroundInClipRect:exposedRect];
|
|
[self drawGridInClipRect:exposedRect];
|
|
[self highlightSelectionInClipRect:exposedRect];
|
|
}
|
|
|
|
- (void)drawBackgroundInClipRect:(CGRect)aRect
|
|
{
|
|
if (!_usesAlternatingRowBackgroundColors)
|
|
return;
|
|
|
|
var rowColors = [self alternatingRowBackgroundColors],
|
|
colorCount = [rowColors count];
|
|
|
|
if (colorCount === 0)
|
|
return;
|
|
|
|
var context = [[CPGraphicsContext currentContext] graphicsPort];
|
|
|
|
if (colorCount === 1)
|
|
{
|
|
CGContextSetFillColor(context, rowColors[0]);
|
|
CGContextFillRect(context, aRect);
|
|
|
|
return;
|
|
}
|
|
// CGContextFillRect(context, CGRectIntersection(aRect, fillRect));
|
|
// console.profile("row-paint");
|
|
var exposedRows = [self rowsInRect:aRect],
|
|
firstRow = exposedRows.location,
|
|
lastRow = CPMaxRange(exposedRows) - 1,
|
|
colorIndex = MIN(exposedRows.length, colorCount),
|
|
heightFilled = 0.0;
|
|
|
|
while (colorIndex--)
|
|
{
|
|
var row = firstRow - firstRow % colorCount + colorIndex,
|
|
fillRect = nil;
|
|
|
|
CGContextBeginPath(context);
|
|
|
|
for (; row <= lastRow; row += colorCount)
|
|
if (row >= firstRow)
|
|
CGContextAddRect(context, CGRectIntersection(aRect, fillRect = [self rectOfRow:row]));
|
|
|
|
if (row - colorCount === lastRow)
|
|
heightFilled = _CGRectGetMaxY(fillRect);
|
|
|
|
CGContextClosePath(context);
|
|
|
|
CGContextSetFillColor(context, rowColors[colorIndex]);
|
|
CGContextFillPath(context);
|
|
}
|
|
// console.profileEnd("row-paint");
|
|
|
|
var totalHeight = _CGRectGetMaxY(aRect);
|
|
|
|
if (heightFilled >= totalHeight || _rowHeight <= 0.0)
|
|
return;
|
|
|
|
var rowHeight = _rowHeight + _intercellSpacing.height,
|
|
fillRect = _CGRectMake(_CGRectGetMinX(aRect), _CGRectGetMinY(aRect) + heightFilled, _CGRectGetWidth(aRect), rowHeight);
|
|
|
|
for (row = lastRow + 1; heightFilled < totalHeight; ++row)
|
|
{
|
|
CGContextSetFillColor(context, rowColors[row % colorCount]);
|
|
CGContextFillRect(context, fillRect);
|
|
|
|
heightFilled += rowHeight;
|
|
fillRect.origin.y += rowHeight;
|
|
}
|
|
}
|
|
|
|
- (void)drawGridInClipRect:(CGRect)aRect
|
|
{
|
|
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
|
gridStyleMask = [self gridStyleMask];
|
|
|
|
if (!(gridStyleMask & (CPTableViewSolidHorizontalGridLineMask | CPTableViewSolidVerticalGridLineMask)))
|
|
return;
|
|
|
|
CGContextBeginPath(context);
|
|
|
|
if (gridStyleMask & CPTableViewSolidHorizontalGridLineMask)
|
|
{
|
|
var exposedRows = [self rowsInRect:aRect];
|
|
row = exposedRows.location,
|
|
lastRow = CPMaxRange(exposedRows) - 1,
|
|
rowY = -0.5,
|
|
minX = _CGRectGetMinX(aRect),
|
|
maxX = _CGRectGetMaxX(aRect);
|
|
|
|
for (; row <= lastRow; ++row)
|
|
{
|
|
// grab each row rect and add the top and bottom lines
|
|
var rowRect = [self rectOfRow:row],
|
|
rowY = _CGRectGetMaxY(rowRect) - 0.5;
|
|
|
|
CGContextMoveToPoint(context, minX, rowY);
|
|
CGContextAddLineToPoint(context, maxX, rowY);
|
|
}
|
|
|
|
if (_rowHeight > 0.0)
|
|
{
|
|
var rowHeight = _rowHeight + _intercellSpacing.height,
|
|
totalHeight = _CGRectGetMaxY(aRect);
|
|
|
|
while (rowY < totalHeight)
|
|
{
|
|
rowY += rowHeight;
|
|
|
|
CGContextMoveToPoint(context, minX, rowY);
|
|
CGContextAddLineToPoint(context, maxX, rowY);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gridStyleMask & CPTableViewSolidVerticalGridLineMask)
|
|
{
|
|
var exposedColumnIndexes = [self columnIndexesInRect:aRect],
|
|
columnsArray = [];
|
|
|
|
[exposedColumnIndexes getIndexes:columnsArray maxCount:-1 inIndexRange:nil];
|
|
|
|
var columnArrayIndex = 0,
|
|
columnArrayCount = columnsArray.length,
|
|
minY = _CGRectGetMinY(aRect),
|
|
maxY = _CGRectGetMaxY(aRect);
|
|
|
|
|
|
for (; columnArrayIndex < columnArrayCount; ++columnArrayIndex)
|
|
{
|
|
var columnRect = [self rectOfColumn:columnsArray[columnArrayIndex]],
|
|
columnX = _CGRectGetMaxX(columnRect) + 0.5;
|
|
|
|
CGContextMoveToPoint(context, columnX, minY);
|
|
CGContextAddLineToPoint(context, columnX, maxY);
|
|
}
|
|
}
|
|
|
|
CGContextClosePath(context);
|
|
CGContextSetStrokeColor(context, [self gridColor]);
|
|
CGContextStrokePath(context);
|
|
}
|
|
|
|
|
|
- (void)highlightSelectionInClipRect:(CGRect)aRect
|
|
{
|
|
if (_selectionHighlightStyle === CPTableViewSelectionHighlightStyleNone)
|
|
return;
|
|
|
|
var context = [[CPGraphicsContext currentContext] graphicsPort],
|
|
indexes = [],
|
|
rectSelector = @selector(rectOfRow:);
|
|
|
|
if ([_selectedRowIndexes count] >= 1)
|
|
{
|
|
var exposedRows = [CPIndexSet indexSetWithIndexesInRange:[self rowsInRect:aRect]],
|
|
firstRow = [exposedRows firstIndex],
|
|
exposedRange = CPMakeRange(firstRow, [exposedRows lastIndex] - firstRow + 1);
|
|
|
|
[_selectedRowIndexes getIndexes:indexes maxCount:-1 inIndexRange:exposedRange];
|
|
}
|
|
|
|
else if ([_selectedColumnIndexes count] >= 1)
|
|
{
|
|
rectSelector = @selector(rectOfColumn:);
|
|
|
|
var exposedColumns = [self columnIndexesInRect:aRect],
|
|
firstColumn = [exposedColumns firstIndex],
|
|
exposedRange = CPMakeRange(firstColumn, [exposedColumns lastIndex] - firstColumn + 1);
|
|
|
|
[_selectedColumnIndexes getIndexes:indexes maxCount:-1 inIndexRange:exposedRange];
|
|
}
|
|
|
|
var count = count2 = [indexes count];
|
|
|
|
if (!count)
|
|
return;
|
|
|
|
var drawGradient = (_selectionHighlightStyle === CPTableViewSelectionHighlightStyleSourceList && [_selectedRowIndexes count] >= 1),
|
|
deltaHeight = 0.5 * (_gridStyleMask & CPTableViewSolidHorizontalGridLineMask);
|
|
|
|
CGContextBeginPath(context);
|
|
|
|
var gradientCache = [self selectionGradientColors],
|
|
topLineColor = [gradientCache objectForKey:CPSourceListTopLineColor],
|
|
bottomLineColor = [gradientCache objectForKey:CPSourceListBottomLineColor],
|
|
gradientColor = [gradientCache objectForKey:CPSourceListGradient];
|
|
|
|
while (count--)
|
|
{
|
|
var rowRect = CGRectIntersection(objj_msgSend(self, rectSelector, indexes[count]), aRect);
|
|
CGContextAddRect(context, rowRect);
|
|
|
|
if (drawGradient)
|
|
{
|
|
var minX = _CGRectGetMinX(rowRect),
|
|
minY = _CGRectGetMinY(rowRect),
|
|
maxX = _CGRectGetMaxX(rowRect),
|
|
maxY = _CGRectGetMaxY(rowRect) - deltaHeight;
|
|
|
|
CGContextDrawLinearGradient(context, gradientColor, rowRect.origin, CGPointMake(minX, maxY), 0);
|
|
CGContextClosePath(context);
|
|
|
|
CGContextBeginPath(context);
|
|
CGContextMoveToPoint(context, minX, minY);
|
|
CGContextAddLineToPoint(context, maxX, minY);
|
|
CGContextClosePath(context);
|
|
CGContextSetStrokeColor(context, topLineColor);
|
|
CGContextStrokePath(context);
|
|
|
|
CGContextBeginPath(context);
|
|
CGContextMoveToPoint(context, minX, maxY);
|
|
CGContextAddLineToPoint(context, maxX, maxY - 1);
|
|
CGContextClosePath(context);
|
|
CGContextSetStrokeColor(context, bottomLineColor);
|
|
CGContextStrokePath(context);
|
|
}
|
|
}
|
|
|
|
CGContextClosePath(context);
|
|
|
|
if (!drawGradient)
|
|
{
|
|
[[self selectionHighlightColor] setFill];
|
|
CGContextFillPath(context);
|
|
}
|
|
|
|
CGContextBeginPath(context);
|
|
gridStyleMask = [self gridStyleMask];
|
|
for(var i=0; i < count2; i++)
|
|
{
|
|
var rect = objj_msgSend(self, rectSelector, indexes[i]),
|
|
minX = CGRectGetMinX(rect) - 0.5,
|
|
maxX = CGRectGetMaxX(rect) - 0.5,
|
|
minY = CGRectGetMinY(rect) - 0.5,
|
|
maxY = CGRectGetMaxY(rect) - 0.5;
|
|
|
|
if ([_selectedRowIndexes count] >= 1 && gridStyleMask & CPTableViewSolidVerticalGridLineMask)
|
|
{
|
|
var exposedColumns = [self columnIndexesInRect:aRect],
|
|
exposedColumnIndexes = [],
|
|
firstExposedColumn = [exposedColumns firstIndex],
|
|
exposedRange = CPMakeRange(firstExposedColumn, [exposedColumns lastIndex] - firstExposedColumn + 1);
|
|
[exposedColumns getIndexes:exposedColumnIndexes maxCount:-1 inIndexRange:exposedRange];
|
|
var exposedColumnCount = [exposedColumnIndexes count];
|
|
|
|
for(var c = firstExposedColumn; c < exposedColumnCount; c++)
|
|
{
|
|
var colRect = [self rectOfColumn:exposedColumnIndexes[c]],
|
|
colX = CGRectGetMaxX(colRect) + 0.5;
|
|
|
|
CGContextMoveToPoint(context, colX, minY);
|
|
CGContextAddLineToPoint(context, colX, maxY);
|
|
}
|
|
|
|
}
|
|
|
|
//if the row after the current row is not selected then there is no need to draw the bottom grid line white.
|
|
if([indexes containsObject:indexes[i]+1])
|
|
{
|
|
CGContextMoveToPoint(context, minX, maxY);
|
|
CGContextAddLineToPoint(context, maxX, maxY);
|
|
}
|
|
}
|
|
|
|
CGContextClosePath(context);
|
|
CGContextSetStrokeColor(context, [self currentValueForThemeAttribute:"highlighted-grid-color"]);
|
|
CGContextStrokePath(context);
|
|
}
|
|
|
|
- (void)_drawRows:(CPIndexSet)rowsIndexes clipRect:(CGRect)clipRect
|
|
{
|
|
var row = [rowsIndexes firstIndex];
|
|
|
|
while (row !== CPNotFound)
|
|
{
|
|
[self drawRow:row clipRect:CGRectIntersection(clipRect, [self rectOfRow:row])];
|
|
row = [rowsIndexes indexGreaterThanIndex:row];
|
|
}
|
|
}
|
|
|
|
- (void)drawRow:(CPInteger)row clipRect:(CGRect)rect
|
|
{
|
|
// This method does currently nothing in cappuccino. Can be overriden by subclasses.
|
|
}
|
|
|
|
- (void)layoutSubviews
|
|
{
|
|
[self load];
|
|
}
|
|
|
|
- (void)viewWillMoveToSuperview:(CPView)aView
|
|
{
|
|
var superview = [self superview],
|
|
defaultCenter = [CPNotificationCenter defaultCenter];
|
|
|
|
if (superview)
|
|
{
|
|
[defaultCenter
|
|
removeObserver:self
|
|
name:CPViewFrameDidChangeNotification
|
|
object:superview];
|
|
|
|
[defaultCenter
|
|
removeObserver:self
|
|
name:CPViewBoundsDidChangeNotification
|
|
object:superview];
|
|
}
|
|
|
|
if (aView)
|
|
{
|
|
[aView setPostsFrameChangedNotifications:YES];
|
|
[aView setPostsBoundsChangedNotifications:YES];
|
|
|
|
[defaultCenter
|
|
addObserver:self
|
|
selector:@selector(superviewFrameChanged:)
|
|
name:CPViewFrameDidChangeNotification
|
|
object:aView];
|
|
|
|
[defaultCenter
|
|
addObserver:self
|
|
selector:@selector(superviewBoundsChanged:)
|
|
name:CPViewBoundsDidChangeNotification
|
|
object:aView];
|
|
}
|
|
}
|
|
|
|
- (void)superviewBoundsChanged:(CPNotification)aNotification
|
|
{
|
|
[self setNeedsDisplay:YES];
|
|
[self setNeedsLayout];
|
|
}
|
|
|
|
- (void)superviewFrameChanged:(CPNotification)aNotification
|
|
{
|
|
[self tile];
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (BOOL)tracksMouseOutsideOfFrame
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (BOOL)startTrackingAt:(CGPoint)aPoint
|
|
{
|
|
var row = [self rowAtPoint:aPoint];
|
|
|
|
//if the user clicks outside a row then deslect everything
|
|
if (row < 0 && _allowsEmptySelection)
|
|
[self selectRowIndexes:[CPIndexSet indexSet] byExtendingSelection:NO];
|
|
|
|
[self _noteSelectionIsChanging];
|
|
|
|
if ([self mouseDownFlags] & CPShiftKeyMask)
|
|
_selectionAnchorRow = (ABS([_selectedRowIndexes firstIndex] - row) < ABS([_selectedRowIndexes lastIndex] - row)) ?
|
|
[_selectedRowIndexes firstIndex] : [_selectedRowIndexes lastIndex];
|
|
else
|
|
_selectionAnchorRow = row;
|
|
|
|
|
|
//set ivars for startTrackingPoint and time...
|
|
_startTrackingPoint = aPoint;
|
|
_startTrackingTimestamp = new Date();
|
|
|
|
if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_)
|
|
_trackingPointMovedOutOfClickSlop = NO;
|
|
|
|
// if the table has drag support then we use mouseUp to select a single row.
|
|
// otherwise it uses mouse down.
|
|
if (row >=0 && !(_implementedDataSourceMethods & CPTableViewDataSource_tableView_writeRowsWithIndexes_toPasteboard_))
|
|
[self _updateSelectionWithMouseAtRow:row];
|
|
|
|
[[self window] makeFirstResponder:self];
|
|
return YES;
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (void)trackMouse:(CPEvent)anEvent
|
|
{
|
|
// Prevent CPControl from eating the mouse events when we are in a drag session
|
|
if (![_draggedRowIndexes count])
|
|
{
|
|
[self autoscroll:anEvent];
|
|
[super trackMouse:anEvent];
|
|
}
|
|
else
|
|
[CPApp sendEvent:anEvent];
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
|
|
{
|
|
var row = [self rowAtPoint:aPoint];
|
|
|
|
// begin the drag is the datasource lets us, we've move at least +-3px vertical or horizontal,
|
|
// or we're dragging from selected rows and we haven't begun a drag session
|
|
if(!_isSelectingSession && _implementedDataSourceMethods & CPTableViewDataSource_tableView_writeRowsWithIndexes_toPasteboard_)
|
|
{
|
|
if (row >= 0 && (ABS(_startTrackingPoint.x - aPoint.x) > 3 || (_verticalMotionCanDrag && ABS(_startTrackingPoint.y - aPoint.y) > 3)) ||
|
|
([_selectedRowIndexes containsIndex:row]))
|
|
{
|
|
if ([_selectedRowIndexes containsIndex:row])
|
|
_draggedRowIndexes = [[CPIndexSet alloc] initWithIndexSet:_selectedRowIndexes];
|
|
else
|
|
_draggedRowIndexes = [CPIndexSet indexSetWithIndex:row];
|
|
|
|
|
|
//ask the datasource for the data
|
|
var pboard = [CPPasteboard pasteboardWithName:CPDragPboard];
|
|
|
|
if ([self canDragRowsWithIndexes:_draggedRowIndexes atPoint:aPoint] && [_dataSource tableView:self writeRowsWithIndexes:_draggedRowIndexes toPasteboard:pboard])
|
|
{
|
|
var currentEvent = [CPApp currentEvent],
|
|
offset = CPPointMakeZero(),
|
|
tableColumns = [_tableColumns objectsAtIndexes:_exposedColumns];
|
|
|
|
// We deviate from the default Cocoa implementation here by asking for a view in stead of an image
|
|
// We support both, but the view prefered over the image because we can mimic the rows we are dragging
|
|
// by re-creating the data views for the dragged rows
|
|
var view = [self dragViewForRowsWithIndexes:_draggedRowIndexes
|
|
tableColumns:tableColumns
|
|
event:currentEvent
|
|
offset:offset];
|
|
|
|
if (!view)
|
|
{
|
|
var image = [self dragImageForRowsWithIndexes:_draggedRowIndexes
|
|
tableColumns:tableColumns
|
|
event:currentEvent
|
|
offset:offset];
|
|
view = [[CPImageView alloc] initWithFrame:CPMakeRect(0, 0, [image size].width, [image size].height)];
|
|
[view setImage:image];
|
|
}
|
|
|
|
var bounds = [view bounds];
|
|
var viewLocation = CPPointMake(aPoint.x - CGRectGetWidth(bounds)/2 + offset.x, aPoint.y - CGRectGetHeight(bounds)/2 + offset.y);
|
|
[self dragView:view at:viewLocation offset:CPPointMakeZero() event:[CPApp currentEvent] pasteboard:pboard source:self slideBack:YES];
|
|
_startTrackingPoint = nil;
|
|
|
|
return NO;
|
|
}
|
|
|
|
// The delegate disallowed the drag so clear the dragged row indexes
|
|
_draggedRowIndexes = [CPIndexSet indexSet];
|
|
}
|
|
else if (ABS(_startTrackingPoint.x - aPoint.x) < 5 && ABS(_startTrackingPoint.y - aPoint.y) < 5)
|
|
return YES;
|
|
}
|
|
|
|
_isSelectingSession = YES;
|
|
if(row >= 0 && row !== _lastTrackedRowIndex)
|
|
{
|
|
_lastTrackedRowIndex = row;
|
|
[self _updateSelectionWithMouseAtRow:row];
|
|
}
|
|
|
|
if ((_implementedDataSourceMethods & CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_)
|
|
&& !_trackingPointMovedOutOfClickSlop)
|
|
{
|
|
var CLICK_SPACE_DELTA = 5.0; // Stolen from AppKit/Platform/DOM/CPPlatformWindow+DOM.j
|
|
if (ABS(aPoint.x - _startTrackingPoint.x) > CLICK_SPACE_DELTA
|
|
|| ABS(aPoint.y - _startTrackingPoint.y) > CLICK_SPACE_DELTA)
|
|
{
|
|
_trackingPointMovedOutOfClickSlop = YES;
|
|
}
|
|
}
|
|
|
|
return YES;
|
|
}
|
|
|
|
/*!
|
|
@ignore
|
|
*/
|
|
- (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
|
|
{
|
|
_isSelectingSession = NO;
|
|
|
|
var CLICK_TIME_DELTA = 1000,
|
|
columnIndex,
|
|
column,
|
|
rowIndex,
|
|
shouldEdit = YES;
|
|
|
|
if(_implementedDataSourceMethods & CPTableViewDataSource_tableView_writeRowsWithIndexes_toPasteboard_)
|
|
{
|
|
rowIndex = [self rowAtPoint:aPoint];
|
|
if (rowIndex !== -1)
|
|
{
|
|
if ([_draggedRowIndexes count] > 0)
|
|
{
|
|
_draggedRowIndexes = [CPIndexSet indexSet];
|
|
return;
|
|
}
|
|
// if the table has drag support then we use mouseUp to select a single row.
|
|
_previouslySelectedRowIndexes = [_selectedRowIndexes copy];
|
|
[self _updateSelectionWithMouseAtRow:rowIndex];
|
|
}
|
|
}
|
|
|
|
if (mouseIsUp
|
|
&& (_implementedDataSourceMethods & CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_)
|
|
&& !_trackingPointMovedOutOfClickSlop
|
|
&& ([[CPApp currentEvent] clickCount] > 1))
|
|
{
|
|
columnIndex = [self columnAtPoint:lastPoint];
|
|
if (columnIndex !== -1)
|
|
{
|
|
column = _tableColumns[columnIndex];
|
|
if ([column isEditable])
|
|
{
|
|
rowIndex = [self rowAtPoint:aPoint];
|
|
if (rowIndex !== -1)
|
|
{
|
|
if (_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldEditTableColumn_row_)
|
|
shouldEdit = [_delegate tableView:self shouldEditTableColumn:column row:rowIndex];
|
|
if (shouldEdit)
|
|
{
|
|
[self editColumn:columnIndex row:rowIndex withEvent:nil select:YES];
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} //end of editing conditional
|
|
|
|
//double click actions
|
|
if([[CPApp currentEvent] clickCount] === 2 && _doubleAction)
|
|
{
|
|
_clickedRow = [self rowAtPoint:aPoint];
|
|
[self sendAction:_doubleAction to:_target];
|
|
}
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (CPDragOperation)draggingEntered:(id)sender
|
|
{
|
|
var location = [self convertPoint:[sender draggingLocation] fromView:nil],
|
|
dropOperation = [self _proposedDropOperationAtPoint:location],
|
|
row = [self _proposedRowAtPoint:location];
|
|
|
|
if(_retargetedDropRow !== nil)
|
|
row = _retargetedDropRow;
|
|
|
|
var draggedTypes = [self registeredDraggedTypes],
|
|
count = [draggedTypes count],
|
|
i = 0;
|
|
|
|
for (; i < count; i++)
|
|
{
|
|
if ([[[sender draggingPasteboard] types] containsObject:[draggedTypes objectAtIndex: i]])
|
|
return [self _validateDrop:sender proposedRow:row proposedDropOperation:dropOperation];
|
|
}
|
|
|
|
return CPDragOperationNone;
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (void)draggingExited:(id)sender
|
|
{
|
|
[_dropOperationFeedbackView removeFromSuperview];
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (void)draggingEnded:(id)sender
|
|
{
|
|
[self _draggingEnded];
|
|
}
|
|
|
|
- (void)_draggingEnded
|
|
{
|
|
_retargetedDropOperation = nil;
|
|
_retargetedDropRow = nil;
|
|
_draggedRowIndexes = [CPIndexSet indexSet];
|
|
[_dropOperationFeedbackView removeFromSuperview];
|
|
}
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (BOOL)wantsPeriodicDraggingUpdates
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (CPTableViewDropOperation)_proposedDropOperationAtPoint:(CGPoint)theDragPoint
|
|
{
|
|
if(_retargetedDropOperation !== nil)
|
|
return _retargetedDropOperation;
|
|
|
|
var row = [self _proposedRowAtPoint:theDragPoint],
|
|
rowRect = [self rectOfRow:row];
|
|
|
|
// If there is no (the default) or to little inter cell spacing we create some room for the CPTableViewDropAbove indicator
|
|
// This probably doesn't work if the row height is smaller than or around 5.0
|
|
if ([self intercellSpacing].height < 5.0)
|
|
rowRect = CPRectInset(rowRect, 0.0, 5.0 - [self intercellSpacing].height);
|
|
|
|
// If the altered row rect contains the drag point we show the drop on
|
|
// We don't show the drop on indicator if we are dragging below the last row
|
|
// in that case we always want to show the drop above indicator
|
|
if (CGRectContainsPoint(rowRect, theDragPoint) && row < _numberOfRows)
|
|
return CPTableViewDropOn;
|
|
|
|
return CPTableViewDropAbove;
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (CPInteger)_proposedRowAtPoint:(CGPoint)dragPoint
|
|
{
|
|
// We don't use rowAtPoint here because the drag indicator can appear below the last row
|
|
// and rowAtPoint doesn't return rows that are larger than numberOfRows
|
|
// FIX ME: this is going to break when we implement variable row heights...
|
|
var row = FLOOR(dragPoint.y / ( _rowHeight + _intercellSpacing.height )),
|
|
// Determine if the mouse is currently closer to this row or the row below it
|
|
lowerRow = row + 1,
|
|
rect = [self rectOfRow:row],
|
|
bottomPoint = CGRectGetMaxY(rect),
|
|
bottomThirty = bottomPoint - ((bottomPoint - CGRectGetMinY(rect)) * 0.3);
|
|
|
|
if (dragPoint.y > MAX(bottomThirty, bottomPoint - 6))
|
|
row = lowerRow;
|
|
|
|
if (row >= [self numberOfRows])
|
|
row = [self numberOfRows];
|
|
|
|
return row;
|
|
}
|
|
|
|
- (void)_validateDrop:(id)info proposedRow:(CPInteger)row proposedDropOperation:(CPTableViewDropOperation)dropOperation
|
|
{
|
|
if(_implementedDataSourceMethods & CPTableViewDataSource_tableView_validateDrop_proposedRow_proposedDropOperation_)
|
|
return [_dataSource tableView:self validateDrop:info proposedRow:row proposedDropOperation:dropOperation];
|
|
|
|
return CPDragOperationNone;
|
|
}
|
|
|
|
- (CPRect)_rectForDropHighlightViewOnRow:(int)theRowIndex
|
|
{
|
|
if (theRowIndex >= [self numberOfRows])
|
|
theRowIndex = [self numberOfRows] - 1;
|
|
|
|
return [self rectOfRow:theRowIndex];
|
|
}
|
|
|
|
- (CPRect)_rectForDropHighlightViewBetweenUpperRow:(int)theUpperRowIndex andLowerRow:(int)theLowerRowIndex offset:(CPPoint)theOffset
|
|
{
|
|
if (theLowerRowIndex > [self numberOfRows])
|
|
theLowerRowIndex = [self numberOfRows];
|
|
|
|
return [self rectOfRow:theLowerRowIndex];
|
|
}
|
|
|
|
- (CPDragOperation)draggingUpdated:(id)sender
|
|
{
|
|
var location = [self convertPoint:[sender draggingLocation] fromView:nil],
|
|
dropOperation = [self _proposedDropOperationAtPoint:location],
|
|
numberOfRows = [self numberOfRows];
|
|
|
|
var row = [self _proposedRowAtPoint:location],
|
|
dragOperation = [self _validateDrop:sender proposedRow:row proposedDropOperation:dropOperation];
|
|
exposedClipRect = [self visibleRect];
|
|
|
|
if(_retargetedDropRow !== nil)
|
|
row = _retargetedDropRow;
|
|
|
|
|
|
if (dropOperation === CPTableViewDropOn && row >= [self numberOfRows])
|
|
row = [self numberOfRows] - 1;
|
|
|
|
var rect = _CGRectMakeZero();
|
|
|
|
if (row === -1)
|
|
rect = exposedClipRect;
|
|
|
|
else if (dropOperation === CPTableViewDropAbove)
|
|
rect = [self _rectForDropHighlightViewBetweenUpperRow:row - 1 andLowerRow:row offset:location];
|
|
|
|
else
|
|
rect = [self _rectForDropHighlightViewOnRow:row];
|
|
|
|
[_dropOperationFeedbackView setDropOperation:row !== -1 ? dropOperation : CPDragOperationNone];
|
|
[_dropOperationFeedbackView setHidden:(dragOperation == CPDragOperationNone)];
|
|
[_dropOperationFeedbackView setFrame:rect];
|
|
[_dropOperationFeedbackView setCurrentRow:row];
|
|
[self addSubview:_dropOperationFeedbackView];
|
|
|
|
return dragOperation;
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (BOOL)prepareForDragOperation:(id)sender
|
|
{
|
|
// FIX ME: is there anything else that needs to happen here?
|
|
// actual validation is called in dragginUpdated:
|
|
[_dropOperationFeedbackView removeFromSuperview];
|
|
|
|
return (_implementedDataSourceMethods & CPTableViewDataSource_tableView_validateDrop_proposedRow_proposedDropOperation_);
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (BOOL)performDragOperation:(id)sender
|
|
{
|
|
var location = [self convertPoint:[sender draggingLocation] fromView:nil];
|
|
operation = [self _proposedDropOperationAtPoint:location],
|
|
row = _retargetedDropRow;
|
|
|
|
if(row === nil)
|
|
var row = [self _proposedRowAtPoint:location];
|
|
|
|
return [_dataSource tableView:self acceptDrop:sender row:row dropOperation:operation];
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
*/
|
|
- (void)concludeDragOperation:(id)sender
|
|
{
|
|
[self reloadData];
|
|
}
|
|
|
|
/*
|
|
//this method is sent to the data source for conviences...
|
|
*/
|
|
- (void)draggedImage:(CPImage)anImage endedAt:(CGPoint)aLocation operation:(CPDragOperation)anOperation
|
|
{
|
|
if([_dataSource respondsToSelector:@selector(tableView:didEndDraggedImage:atPosition:operation:)])
|
|
[_dataSource tableView:self didEndDraggedImage:anImage atPosition:aLocation operation:anOperation];
|
|
}
|
|
|
|
/*
|
|
@ignore
|
|
we're using this because we drag views instead of images so we can get the rows themselves to actually drag
|
|
*/
|
|
- (void)draggedView:(CPImage)aView endedAt:(CGPoint)aLocation operation:(CPDragOperation)anOperation
|
|
{
|
|
[self _draggingEnded];
|
|
[self draggedImage:aView endedAt:aLocation operation:anOperation];
|
|
}
|
|
|
|
- (void)_updateSelectionWithMouseAtRow:(CPInteger)aRow
|
|
{
|
|
//check to make sure the row exists
|
|
if(aRow < 0)
|
|
return;
|
|
|
|
var newSelection,
|
|
shouldExtendSelection = NO;
|
|
// If cmd/ctrl was held down XOR the old selection with the proposed selection
|
|
if ([self mouseDownFlags] & (CPCommandKeyMask | CPControlKeyMask | CPAlternateKeyMask))
|
|
{
|
|
if ([_selectedRowIndexes containsIndex:aRow])
|
|
{
|
|
newSelection = [_selectedRowIndexes copy];
|
|
|
|
[newSelection removeIndex:aRow];
|
|
}
|
|
|
|
else if (_allowsMultipleSelection)
|
|
{
|
|
newSelection = [_selectedRowIndexes copy];
|
|
|
|
[newSelection addIndex:aRow];
|
|
}
|
|
|
|
else
|
|
newSelection = [CPIndexSet indexSetWithIndex:aRow];
|
|
}
|
|
|
|
else if (_allowsMultipleSelection)
|
|
{
|
|
newSelection = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(MIN(aRow, _selectionAnchorRow), ABS(aRow - _selectionAnchorRow) + 1)];
|
|
shouldExtendSelection = [self mouseDownFlags] & CPShiftKeyMask &&
|
|
((_lastSelectedRow == [_selectedRowIndexes lastIndex] && aRow > _lastSelectedRow) ||
|
|
(_lastSelectedRow == [_selectedRowIndexes firstIndex] && aRow < _lastSelectedRow));
|
|
}
|
|
|
|
else if (aRow >= 0 && aRow < _numberOfRows)
|
|
newSelection = [CPIndexSet indexSetWithIndex:aRow];
|
|
|
|
else
|
|
newSelection = [CPIndexSet indexSet];
|
|
|
|
if ([newSelection isEqualToIndexSet:_selectedRowIndexes])
|
|
return;
|
|
|
|
if (_implementedDelegateMethods & CPTableViewDelegate_selectionShouldChangeInTableView_ &&
|
|
![_delegate selectionShouldChangeInTableView:self])
|
|
return;
|
|
|
|
if (_implementedDelegateMethods & CPTableViewDelegate_tableView_selectionIndexesForProposedSelection_)
|
|
newSelection = [_delegate tableView:self selectionIndexesForProposedSelection:newSelection];
|
|
|
|
if (_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldSelectRow_)
|
|
{
|
|
var indexArray = [];
|
|
|
|
[newSelection getIndexes:indexArray maxCount:-1 inIndexRange:nil];
|
|
|
|
var indexCount = indexArray.length;
|
|
|
|
while (indexCount--)
|
|
{
|
|
var index = indexArray[indexCount];
|
|
|
|
if (![_delegate tableView:self shouldSelectRow:index])
|
|
[newSelection removeIndex:index];
|
|
}
|
|
}
|
|
|
|
// if empty selection is not allowed and the new selection has nothing selected, abort
|
|
if (!_allowsEmptySelection && [newSelection count] === 0)
|
|
return;
|
|
|
|
if ([newSelection isEqualToIndexSet:_selectedRowIndexes])
|
|
return;
|
|
|
|
[self selectRowIndexes:newSelection byExtendingSelection:shouldExtendSelection];
|
|
}
|
|
|
|
- (void)_noteSelectionIsChanging
|
|
{
|
|
[[CPNotificationCenter defaultCenter]
|
|
postNotificationName:CPTableViewSelectionIsChangingNotification
|
|
object:self
|
|
userInfo:nil];
|
|
}
|
|
|
|
- (void)_noteSelectionDidChange
|
|
{
|
|
[[CPNotificationCenter defaultCenter]
|
|
postNotificationName:CPTableViewSelectionDidChangeNotification
|
|
object:self
|
|
userInfo:nil];
|
|
}
|
|
|
|
- (BOOL)becomeFirstResponder
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)acceptsFirstResponder
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (void)keyDown:(CPEvent)anEvent
|
|
{
|
|
[self interpretKeyEvents:[anEvent]];
|
|
}
|
|
|
|
- (void)moveDown:(id)sender
|
|
{
|
|
if (_implementedDelegateMethods & CPTableViewDelegate_selectionShouldChangeInTableView_ &&
|
|
![_delegate selectionShouldChangeInTableView:self])
|
|
return;
|
|
|
|
var anEvent = [CPApp currentEvent];
|
|
if([[self selectedRowIndexes] count] > 0)
|
|
{
|
|
var extend = NO;
|
|
|
|
if(([anEvent modifierFlags] & CPShiftKeyMask) && _allowsMultipleSelection)
|
|
extend = YES;
|
|
|
|
var i = [[self selectedRowIndexes] lastIndex];
|
|
if(i<[self numberOfRows] - 1)
|
|
i++; //set index to the next row after the last row selected
|
|
}
|
|
else
|
|
{
|
|
var extend = NO;
|
|
//no rows are currently selected
|
|
if([self numberOfRows] > 0)
|
|
var i = 0; //select the first row
|
|
}
|
|
|
|
|
|
if(_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldSelectRow_)
|
|
{
|
|
|
|
while((![_delegate tableView:self shouldSelectRow:i]) && i<[self numberOfRows])
|
|
{
|
|
//check to see if the row can be selected if it can't be then see if the next row can be selected
|
|
i++;
|
|
}
|
|
|
|
//if the index still can be selected after the loop then just return
|
|
if(![_delegate tableView:self shouldSelectRow:i])
|
|
return;
|
|
}
|
|
|
|
[self selectRowIndexes:[CPIndexSet indexSetWithIndex:i] byExtendingSelection:extend];
|
|
|
|
if(i >= 0)
|
|
[self scrollRowToVisible:i];
|
|
}
|
|
|
|
- (void)moveDownAndModifySelection:(id)sender
|
|
{
|
|
[self moveDown:sender];
|
|
}
|
|
|
|
- (void)moveUp:(id)sender
|
|
{
|
|
if (_implementedDelegateMethods & CPTableViewDelegate_selectionShouldChangeInTableView_ &&
|
|
![_delegate selectionShouldChangeInTableView:self])
|
|
return;
|
|
|
|
var anEvent = [CPApp currentEvent];
|
|
if([[self selectedRowIndexes] count] > 0)
|
|
{
|
|
var extend = NO;
|
|
|
|
if(([anEvent modifierFlags] & CPShiftKeyMask) && _allowsMultipleSelection)
|
|
extend = YES;
|
|
|
|
var i = [[self selectedRowIndexes] firstIndex];
|
|
if(i > 0)
|
|
i--; //set index to the prev row before the first row selected
|
|
}
|
|
else
|
|
{
|
|
var extend = NO;
|
|
//no rows are currently selected
|
|
if([self numberOfRows] > 0)
|
|
var i = [self numberOfRows] - 1; //select the first row
|
|
}
|
|
|
|
|
|
if(_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldSelectRow_)
|
|
{
|
|
|
|
while((![_delegate tableView:self shouldSelectRow:i]) && i > 0)
|
|
{
|
|
//check to see if the row can be selected if it can't be then see if the prev row can be selected
|
|
i--;
|
|
}
|
|
|
|
//if the index still can be selected after the loop then just return
|
|
if(![_delegate tableView:self shouldSelectRow:i])
|
|
return;
|
|
}
|
|
|
|
[self selectRowIndexes:[CPIndexSet indexSetWithIndex:i] byExtendingSelection:extend];
|
|
|
|
if(i >= 0)
|
|
[self scrollRowToVisible:i];
|
|
}
|
|
|
|
- (void)moveUpAndModifySelection:(id)sender
|
|
{
|
|
[self moveUp:sender];
|
|
}
|
|
|
|
- (void)deleteBackward:(id)sender
|
|
{
|
|
if([_delegate respondsToSelector: @selector(tableViewDeleteKeyPressed:)])
|
|
[_delegate tableViewDeleteKeyPressed:self];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPTableView (Bindings)
|
|
|
|
- (CPString)_replacementKeyPathForBinding:(CPString)aBinding
|
|
{
|
|
if (aBinding === @"selectionIndexes")
|
|
return @"selectedRowIndexes";
|
|
|
|
return [super _replacementKeyPathForBinding:aBinding];
|
|
}
|
|
|
|
- (void)_establishBindingsIfUnbound:(id)destination
|
|
{
|
|
if ([[self infoForBinding:@"content"] objectForKey:CPObservedObjectKey] !== destination)
|
|
[self bind:@"content" toObject:destination withKeyPath:@"arrangedObjects" options:nil];
|
|
|
|
if ([[self infoForBinding:@"selectionIndexes"] objectForKey:CPObservedObjectKey] !== destination)
|
|
[self bind:@"selectionIndexes" toObject:destination withKeyPath:@"selectionIndexes" options:nil];
|
|
|
|
//[self bind:@"sortDescriptors" toObject:destination withKeyPath:@"sortDescriptors" options:nil];
|
|
}
|
|
|
|
- (void)setContent:(CPArray)content
|
|
{
|
|
[self reloadData];
|
|
}
|
|
|
|
@end
|
|
|
|
var CPTableViewDataSourceKey = @"CPTableViewDataSourceKey",
|
|
CPTableViewDelegateKey = @"CPTableViewDelegateKey",
|
|
CPTableViewHeaderViewKey = @"CPTableViewHeaderViewKey",
|
|
CPTableViewTableColumnsKey = @"CPTableViewTableColumnsKey",
|
|
CPTableViewRowHeightKey = @"CPTableViewRowHeightKey",
|
|
CPTableViewIntercellSpacingKey = @"CPTableViewIntercellSpacingKey",
|
|
CPTableViewSelectionHighlightStyleKey = @"CPTableViewSelectionHighlightStyleKey",
|
|
CPTableViewMultipleSelectionKey = @"CPTableViewMultipleSelectionKey",
|
|
CPTableViewEmptySelectionKey = @"CPTableViewEmptySelectionKey",
|
|
CPTableViewColumnReorderingKey = @"CPTableViewColumnReorderingKey",
|
|
CPTableViewColumnResizingKey = @"CPTableViewColumnResizingKey",
|
|
CPTableViewColumnSelectionKey = @"CPTableViewColumnSelectionKey",
|
|
CPTableViewColumnAutoresizingStyleKey = @"CPTableViewColumnAutoresizingStyleKey",
|
|
CPTableViewGridColorKey = @"CPTableViewGridColorKey",
|
|
CPTableViewGridStyleMaskKey = @"CPTableViewGridStyleMaskKey",
|
|
CPTableViewUsesAlternatingBackgroundKey = @"CPTableViewUsesAlternatingBackgroundKey",
|
|
CPTableViewAlternatingRowColorsKey = @"CPTableViewAlternatingRowColorsKey",
|
|
CPTableViewHeaderViewKey = @"CPTableViewHeaderViewKey",
|
|
CPTableViewCornerViewKey = @"CPTableViewCornerViewKey";
|
|
|
|
@implementation CPTableView (CPCoding)
|
|
|
|
- (id)initWithCoder:(CPCoder)aCoder
|
|
{
|
|
self = [super initWithCoder:aCoder];
|
|
|
|
if (self)
|
|
{
|
|
//Configuring Behavior
|
|
_allowsColumnReordering = [aCoder decodeBoolForKey:CPTableViewColumnReorderingKey];
|
|
_allowsColumnResizing = [aCoder decodeBoolForKey:CPTableViewColumnResizingKey];
|
|
_allowsMultipleSelection = [aCoder decodeBoolForKey:CPTableViewMultipleSelectionKey];
|
|
_allowsEmptySelection = [aCoder decodeBoolForKey:CPTableViewEmptySelectionKey];
|
|
_allowsColumnSelection = [aCoder decodeBoolForKey:CPTableViewColumnSelectionKey];
|
|
|
|
//Setting Display Attributes
|
|
_selectionHighlightStyle = [aCoder decodeIntForKey:CPTableViewSelectionHighlightStyleKey];
|
|
_columnAutoResizingStyle = [aCoder decodeIntForKey:CPTableViewColumnAutoresizingStyleKey];
|
|
|
|
_tableColumns = [aCoder decodeObjectForKey:CPTableViewTableColumnsKey] || [];
|
|
[_tableColumns makeObjectsPerformSelector:@selector(setTableView:) withObject:self];
|
|
|
|
if ([aCoder containsValueForKey:CPTableViewRowHeightKey])
|
|
_rowHeight = [aCoder decodeFloatForKey:CPTableViewRowHeightKey];
|
|
else
|
|
_rowHeight = 23.0;
|
|
|
|
_intercellSpacing = [aCoder decodeSizeForKey:CPTableViewIntercellSpacingKey] || _CGSizeMake(3.0, 2.0);
|
|
|
|
[self setGridColor:[aCoder decodeObjectForKey:CPTableViewGridColorKey] || [CPColor grayColor]];
|
|
_gridStyleMask = [aCoder decodeIntForKey:CPTableViewGridStyleMaskKey] || CPTableViewGridNone;
|
|
|
|
_usesAlternatingRowBackgroundColors = [aCoder decodeObjectForKey:CPTableViewUsesAlternatingBackgroundKey];
|
|
[self setAlternatingRowBackgroundColors:[aCoder decodeObjectForKey:CPTableViewAlternatingRowColorsKey]];
|
|
|
|
_headerView = [aCoder decodeObjectForKey:CPTableViewHeaderViewKey];
|
|
_cornerView = [aCoder decodeObjectForKey:CPTableViewCornerViewKey];
|
|
|
|
_dataSource = [aCoder decodeObjectForKey:CPTableViewDataSourceKey];
|
|
_delegate = [aCoder decodeObjectForKey:CPTableViewDelegateKey];
|
|
|
|
[self _init];
|
|
|
|
[self viewWillMoveToSuperview:[self superview]];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)encodeWithCoder:(CPCoder)aCoder
|
|
{
|
|
[super encodeWithCoder:aCoder];
|
|
|
|
[aCoder encodeObject:_dataSource forKey:CPTableViewDataSourceKey];
|
|
[aCoder encodeObject:_delegate forKey:CPTableViewDelegateKey];
|
|
|
|
[aCoder encodeFloat:_rowHeight forKey:CPTableViewRowHeightKey];
|
|
[aCoder encodeSize:_intercellSpacing forKey:CPTableViewIntercellSpacingKey];
|
|
|
|
[aCoder encodeInt:_selectionHighlightStyle forKey:CPTableViewSelectionHighlightStyleKey];
|
|
[aCoder encodeInt:_columnAutoResizingStyle forKey:CPTableViewColumnAutoresizingStyleKey];
|
|
|
|
[aCoder encodeBool:_allowsMultipleSelection forKey:CPTableViewMultipleSelectionKey];
|
|
[aCoder encodeBool:_allowsEmptySelection forKey:CPTableViewEmptySelectionKey];
|
|
[aCoder encodeBool:_allowsColumnReordering forKey:CPTableViewColumnReorderingKey];
|
|
[aCoder encodeBool:_allowsColumnResizing forKey:CPTableViewColumnResizingKey];
|
|
[aCoder encodeBool:_allowsColumnSelection forKey:CPTableViewColumnSelectionKey];
|
|
|
|
[aCoder encodeObject:_tableColumns forKey:CPTableViewTableColumnsKey];
|
|
|
|
[aCoder encodeObject:[self gridColor] forKey:CPTableViewGridColorKey];
|
|
[aCoder encodeInt:_gridStyleMask forKey:CPTableViewGridStyleMaskKey];
|
|
|
|
[aCoder encodeBool:_usesAlternatingRowBackgroundColors forKey:CPTableViewUsesAlternatingBackgroundKey];
|
|
[aCoder encodeObject:[self alternatingRowBackgroundColors] forKey:CPTableViewAlternatingRowColorsKey]
|
|
|
|
[aCoder encodeObject:_cornerView forKey:CPTableViewCornerViewKey];
|
|
[aCoder encodeObject:_headerView forKey:CPTableViewHeaderViewKey];
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPIndexSet (tableview)
|
|
|
|
- (void)removeMatches:otherSet
|
|
{
|
|
var firstindex = [self firstIndex];
|
|
var index = MIN(firstindex,[otherSet firstIndex]);
|
|
var switchFlag = (index == firstindex);
|
|
while(index != CPNotFound)
|
|
{
|
|
var indexSet = (switchFlag) ? otherSet : self;
|
|
otherIndex = [indexSet indexGreaterThanOrEqualToIndex:index];
|
|
if (otherIndex == index)
|
|
{
|
|
[self removeIndex:index];
|
|
[otherSet removeIndex:index];
|
|
}
|
|
index = otherIndex;
|
|
switchFlag = !switchFlag;
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation _CPDropOperationDrawingView : CPView
|
|
{
|
|
unsigned dropOperation @accessors;
|
|
CPTableView tableView @accessors;
|
|
int currentRow @accessors;
|
|
BOOL isBlinking @accessors;
|
|
}
|
|
|
|
- (void)drawRect:(CGRect)aRect
|
|
{
|
|
if(tableView._destinationDragStyle === CPTableViewDraggingDestinationFeedbackStyleNone || isBlinking)
|
|
return;
|
|
|
|
var context = [[CPGraphicsContext currentContext] graphicsPort];
|
|
|
|
CGContextSetStrokeColor(context, [CPColor colorWithHexString:@"4886ca"]);
|
|
CGContextSetLineWidth(context, 3);
|
|
|
|
if (currentRow === -1)
|
|
{
|
|
CGContextStrokeRect(context, [self bounds]);
|
|
}
|
|
|
|
else if (dropOperation === CPTableViewDropOn)
|
|
{
|
|
//if row is selected don't fill and stroke white
|
|
var selectedRows = [tableView selectedRowIndexes],
|
|
newRect = _CGRectMake(aRect.origin.x + 2, aRect.origin.y + 2, aRect.size.width - 4, aRect.size.height - 5);
|
|
|
|
if([selectedRows containsIndex:currentRow])
|
|
{
|
|
CGContextSetLineWidth(context, 2);
|
|
CGContextSetStrokeColor(context, [CPColor whiteColor]);
|
|
}
|
|
else
|
|
{
|
|
CGContextSetFillColor(context, [CPColor colorWithRed:72/255 green:134/255 blue:202/255 alpha:0.25]);
|
|
CGContextFillRoundedRectangleInRect(context, newRect, 8, YES, YES, YES, YES);
|
|
}
|
|
CGContextStrokeRoundedRectangleInRect(context, newRect, 8, YES, YES, YES, YES);
|
|
|
|
}
|
|
else if (dropOperation === CPTableViewDropAbove)
|
|
{
|
|
//reposition the view up a tad
|
|
[self setFrameOrigin:CGPointMake(_frame.origin.x, _frame.origin.y - 8)];
|
|
|
|
var selectedRows = [tableView selectedRowIndexes];
|
|
|
|
if([selectedRows containsIndex:currentRow - 1] || [selectedRows containsIndex:currentRow])
|
|
{
|
|
CGContextSetStrokeColor(context, [CPColor whiteColor]);
|
|
CGContextSetLineWidth(context, 4);
|
|
//draw the circle thing
|
|
CGContextStrokeEllipseInRect(context, _CGRectMake(aRect.origin.x + 4, aRect.origin.y + 4, 8, 8));
|
|
//then draw the line
|
|
CGContextBeginPath(context);
|
|
CGContextMoveToPoint(context, 10, aRect.origin.y + 8);
|
|
CGContextAddLineToPoint(context, aRect.size.width - aRect.origin.y - 8, aRect.origin.y + 8);
|
|
CGContextClosePath(context);
|
|
CGContextStrokePath(context);
|
|
|
|
CGContextSetStrokeColor(context, [CPColor colorWithHexString:@"4886ca"]);
|
|
CGContextSetLineWidth(context, 3);
|
|
}
|
|
|
|
//draw the circle thing
|
|
CGContextStrokeEllipseInRect(context, _CGRectMake(aRect.origin.x + 4, aRect.origin.y + 4, 8, 8));
|
|
//then draw the line
|
|
CGContextBeginPath(context);
|
|
CGContextMoveToPoint(context, 10, aRect.origin.y + 8);
|
|
CGContextAddLineToPoint(context, aRect.size.width - aRect.origin.y - 8, aRect.origin.y + 8);
|
|
CGContextClosePath(context);
|
|
CGContextStrokePath(context);
|
|
//CGContextStrokeLineSegments(context, [aRect.origin.x + 8, aRect.origin.y + 8, 300 , aRect.origin.y + 8]);
|
|
}
|
|
}
|
|
|
|
- (void)blink
|
|
{
|
|
if (dropOperation !== CPTableViewDropOn)
|
|
return;
|
|
|
|
isBlinking = YES;
|
|
|
|
var showCallback = function() {
|
|
objj_msgSend(self, "setHidden:", NO)
|
|
isBlinking = NO;
|
|
}
|
|
|
|
var hideCallback = function() {
|
|
objj_msgSend(self, "setHidden:", YES)
|
|
isBlinking = YES;
|
|
}
|
|
|
|
objj_msgSend(self, "setHidden:", YES);
|
|
[CPTimer scheduledTimerWithTimeInterval:0.1 callback:showCallback repeats:NO];
|
|
[CPTimer scheduledTimerWithTimeInterval:0.19 callback:hideCallback repeats:NO];
|
|
[CPTimer scheduledTimerWithTimeInterval:0.27 callback:showCallback repeats:NO];
|
|
}
|
|
@end
|
|
|
|
@implementation _CPColumnDragView : CPView
|
|
{
|
|
CPColor _lineColor;
|
|
}
|
|
|
|
- (id)initWithLineColor:(CPColor)aColor
|
|
{
|
|
self = [super initWithFrame:_CGRectMakeZero()];
|
|
|
|
if (self)
|
|
_lineColor = aColor;
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)drawRect:(CGRect)aRect
|
|
{
|
|
var context = [[CPGraphicsContext currentContext] graphicsPort];
|
|
|
|
CGContextSetStrokeColor(context, _lineColor);
|
|
|
|
var points = [
|
|
_CGPointMake(0.5, 0),
|
|
_CGPointMake(0.5, aRect.size.height)
|
|
];
|
|
|
|
CGContextStrokeLineSegments(context, points, 2);
|
|
|
|
points = [
|
|
_CGPointMake(aRect.size.width - 0.5, 0),
|
|
_CGPointMake(aRect.size.width - 0.5, aRect.size.height)
|
|
];
|
|
|
|
CGContextStrokeLineSegments(context, points, 2);
|
|
}
|
|
@end
|