Files
cappuccino/AppKit/CPTableView.j
T

3345 lines
108 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_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;
// TODO: add docs
CPTableViewSelectionHighlightStyleNone = -1,
CPTableViewSelectionHighlightStyleRegular = 0,
CPTableViewSelectionHighlightStyleSourceList = 1;
CPTableViewGridNone = 0;
CPTableViewSolidVerticalGridLineMask = 1 << 0;
CPTableViewSolidHorizontalGridLineMask = 1 << 1;
CPTableViewNoColumnAutoresizing = 0,
CPTableViewUniformColumnAutoresizingStyle = 1,
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;
CPColor _gridColor;
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;
unsigned _columnAutoResizingStyle;
CGPoint _originalMouseDownPoint;
BOOL _verticalMotionCanDrag;
unsigned _destinationDragStyle;
BOOL _isSelectingSession;
CPIndexSet _draggedRowIndexes;
_dropOperationDrawingView _dropOperationFeedbackView;
CPDragOperation _dragOperationDefaultMask;
int _retargetedDropRow;
CPDragOperation _retargetedDropOperation;
BOOL _disableAutomaticResizing @accessors(property=disableAutomaticResizing);
BOOL _lastColumnShouldSnap;
CPGradient _sourceListActiveGradient;
CPColor _sourceListActiveTopLineColor;
CPColor _sourceListActiveBottomLineColor;
/*
CPGradient _sourceListInactiveGradient;
CPColor _sourceListInactiveTopLineColor;
CPColor _sourceListInactiveBottomLineColor;
*/
}
- (id)initWithFrame:(CGRect)aFrame
{
self = [super initWithFrame:aFrame];
if (self)
{
//Configuring Behavior
_allowsColumnReordering = YES;
_allowsColumnResizing = YES;
_allowsMultipleSelection = NO;
_allowsEmptySelection = YES;
_allowsColumnSelection = NO;
_disableAutomaticResizing = NO;
_tableViewFlags = 0;
//Setting Display Attributes
_selectionHighlightStyle = CPTableViewSelectionHighlightStyleRegular;
[self setUsesAlternatingRowBackgroundColors:NO];
[self setAlternatingRowBackgroundColors:[[CPColor whiteColor], [CPColor colorWithHexString:@"f5f9fc"]]];
_tableColumns = [];
_tableColumnRanges = [];
_dirtyTableColumnRangeIndex = CPNotFound;
_numberOfHiddenColumns = 0;
_objectValues = { };
_dataViewsForTableColumns = { };
_dataViews= [];
_numberOfRows = 0;
_exposedRows = [CPIndexSet indexSet];
_exposedColumns = [CPIndexSet indexSet];
_cachedDataViews = { };
_intercellSpacing = _CGSizeMake(0.0, 0.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 = [[_CPCornerView alloc] initWithFrame:CGRectMake(0, 0, [CPScroller scrollerWidth], CGRectGetHeight([_headerView frame]))];
_lastSelectedRow = -1;
_selectedColumnIndexes = [CPIndexSet indexSet];
_selectedRowIndexes = [CPIndexSet indexSet];
_currentHighlightedTableColumn = nil;
_sortDescriptors = [CPArray array];
_draggedRowIndexes = [CPIndexSet indexSet];
_verticalMotionCanDrag = YES;
_isSelectingSession = NO;
_retargetedDropRow = nil;
_retargetedDropOperation = nil;
_dragOperationDefaultMask = nil;
_destinationDragStyle = CPTableViewDraggingDestinationFeedbackStyleRegular;
_dropOperationFeedbackView = [[_dropOperationDrawingView alloc] initWithFrame:_CGRectMakeZero()];
[self addSubview:_dropOperationFeedbackView];
[_dropOperationFeedbackView setHidden:YES];
[_dropOperationFeedbackView setTableView:self];
_tableDrawView = [[_CPTableDrawView alloc] initWithTableView:self];
[_tableDrawView setBackgroundColor:[CPColor clearColor]];
[self addSubview:_tableDrawView];
[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
{
_lastColumnShouldSnap = NO;
// Gradients for the source list
_sourceListActiveGradient = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(), [89.0/255.0, 153.0/255.0, 209.0/255.0,1.0, 33.0/255.0, 94.0/255.0, 208.0/255.0,1.0], [0,1], 2);
_sourceListActiveTopLineColor = [CPColor colorWithCalibratedRed:(61.0/255.0) green:(123.0/255.0) blue:(218.0/255.0) alpha:1.0];
_sourceListActiveBottomLineColor = [CPColor colorWithCalibratedRed:(31.0/255.0) green:(92.0/255.0) blue:(207.0/255.0) alpha:1.0];
/* //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];*/
}
/*!
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;
if (![_dataSource respondsToSelector:@selector(numberOfRowsInTableView:)])
[CPException raise:CPInternalInconsistencyException
reason:[aDataSource description] + " does not implement numberOfRowsInTableView:."];
if (![_dataSource respondsToSelector:@selector(tableView:objectValueForTableColumn:row:)])
[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
* - 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);
[self 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
{
if ([_alternatingRowBackgroundColors isEqual:alternatingRowBackgroundColors])
return;
_alternatingRowBackgroundColors = alternatingRowBackgroundColors;
[self setNeedsDisplay:YES];
}
- (CPArray)alternatingRowBackgroundColors
{
return _alternatingRowBackgroundColors;
}
- (unsigned)selectionHighlightStyle
{
return _selectionHighlightStyle;
}
- (void)setSelectionHighlightStyle:(unsigned)aSelectionHighlightStyle
{
//early return for IE.
if (aSelectionHighlightStyle == CPTableViewSelectionHighlightStyleSourceList && !CPFeatureIsCompatible(CPHTMLCanvasFeature))
return;
_selectionHighlightStyle = aSelectionHighlightStyle;
if (aSelectionHighlightStyle === CPTableViewSelectionHighlightStyleSourceList)
_destinationDragStyle = CPTableViewDraggingDestinationFeedbackStyleSourceList;
else
_destinationDragStyle = CPTableViewDraggingDestinationFeedbackStyleRegular;
}
/*!
Sets the grid color in the non highlighted state.
@param aColor a CPColor
*/
- (void)setGridColor:(CPColor)aColor
{
if (_gridColor === aColor)
return;
_gridColor = aColor;
[self setNeedsDisplay:YES];
}
- (CPColor)gridColor
{
return _gridColor;
}
/*!
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 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;
[aTableColumn setTableView:nil];
[_tableColumns removeObjectAtIndex:index];
var tableColumnUID = [aTableColumn UID];
if (_objectValues[tableColumnUID])
_objectValues[tableColumnUID] = nil;
if (_dirtyTableColumnRangeIndex < 0)
_dirtyTableColumnRangeIndex = index;
else
_dirtyTableColumnRangeIndex = MIN(index, _dirtyTableColumnRangeIndex);
[self setNeedsLayout];
}
/*!
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);
if (toIndex > fromIndex)
--toIndex;
var tableColumn = _tableColumns[fromIndex];
[_tableColumns removeObjectAtIndex:fromIndex];
[_tableColumns insertObject:tableColumn atIndex:toIndex];
[self setNeedsLayout];
}
- (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];
[_tableDrawView display]; // FIXME: should be setNeedsDisplayInRect:enclosing rect of new (de)selected columns
// but currently -drawRect: is not implemented here
[_headerView setNeedsDisplay:YES];
[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];
[_headerView setNeedsDisplay:YES];
}
var previousSelectedIndexes = [_selectedRowIndexes copy];
if (shouldExtendSelection)
[_selectedRowIndexes addIndexes:rows];
else
_selectedRowIndexes = [rows copy];
[self _updateHighlightWithOldRows:previousSelectedIndexes newRows:_selectedRowIndexes];
[_tableDrawView display]; // FIXME: should be setNeedsDisplayInRect:enclosing rect of new (de)selected rows
// but currently -drawRect: is not implemented here
[self _noteSelectionDidChange];
}
- (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--)
{
var rowIndex = deselectRows[count];
var view = dataViewsInTableColumn[rowIndex];
[view unsetThemeState:CPThemeStateHighlighted];
}
count = selectRows.length;
while (count--)
{
var rowIndex = selectRows[count];
var view = dataViewsInTableColumn[rowIndex];
[view setThemeState:CPThemeStateHighlighted];
}
}
}
- (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:CPThemeStateHighlighted];
}
var headerView = [_tableColumns[columnIndex] headerView];
[headerView unsetThemeState:CPThemeStateHighlighted];
}
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:CPThemeStateHighlighted];
}
var headerView = [_tableColumns[columnIndex] headerView];
[headerView setThemeState:CPThemeStateHighlighted];
}
}
- (CPIndexSet)selectedColumnIndexes
{
return _selectedColumnIndexes;
}
- (CPIndexSet)selectedRowIndexes
{
return _selectedRowIndexes;
}
- (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 (!_dataSource)
return 0;
return [_dataSource numberOfRowsInTableView:self];
}
//Displaying Cell
/*
* - preparedCellAtColumn:row:
*/
//Editing Cells
/*
* - editColumn:row:withEvent:select:
* - editedColumn
* - editedRow
*/
//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;
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])
_tableColumnRanges[index] = CPMakeRange(x, 0.0);
else
{
var width = [_tableColumns[index] width];
_tableColumnRanges[index] = CPMakeRange(x, width);
x += width;
}
}
_tableColumnRanges.length = count;
_dirtyTableColumnRangeIndex = CPNotFound;
}
// Complexity:
// O(1)
- (CGRect)rectOfColumn:(CPInteger)aColumnIndex
{
aColumnIndex = +aColumnIndex;
if (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
{
if (NO)
return NULL;
// FIXME: WRONG: ASK TABLE COLUMN RANGE
return _CGRectMake(0.0, (aRowIndex * (_rowHeight + _intercellSpacing.height)), _CGRectGetWidth([self bounds]), _rowHeight);
}
// 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 receivers 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;
var 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];
return _CGRectMake(tableColumnRange.location, _CGRectGetMinY(rectOfRow), tableColumnRange.length, _CGRectGetHeight(rectOfRow));
}
//FIX ME: We should refactor this!
- (void)resizeWithOldSuperviewSize:(CGSize)aSize
{
[super resizeWithOldSuperviewSize:aSize];
if (_disableAutomaticResizing)
return;
var mask = _columnAutoResizingStyle;
if(mask === CPTableViewUniformColumnAutoresizingStyle)
{
[self _resizeAllColumnUniformlyWithOldSize:aSize];
}
if(mask === CPTableViewLastColumnOnlyAutoresizingStyle)
{
[self sizeLastColumnToFit];
}
if(mask === CPTableViewFirstColumnOnlyAutoresizingStyle)
{
var superview = [self superview];
if (!superview)
return;
var superviewSize = [superview bounds].size;
UPDATE_COLUMN_RANGES_IF_NECESSARY();
var count = NUMBER_OF_COLUMNS();
var visColumns = [[CPArray alloc] init];
var totalWidth = 0;
for(var i=0; i < count; i++)
{
if(![_tableColumns[i] isHidden])
{
[visColumns addObject:i];
totalWidth += [_tableColumns[i] width];
}
}
count = [visColumns count];
//if there are rows
if (count > 0)
{
var columnToResize = _tableColumns[visColumns[0]];
var newWidth = superviewSize.width - totalWidth;// - [columnToResize width];
newWidth += [columnToResize width];
newWidth = (newWidth < [columnToResize minWidth]) ? [columnToResize minWidth] : newWidth;
newWidth = (newWidth > [columnToResize maxWidth]) ? [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;
if (_dirtyTableColumnRangeIndex !== CPNotFound) [self _recalculateTableColumnRanges];//UPDATE_COLUMN_RANGES_IF_NECESSARY();
var count = _tableColumns.length,//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 = MAX([columnToResize minWidth], realNewWidth);
newWidth = MIN([columnToResize maxWidth], realNewWidth);
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 theres 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];
var newSize = MAX(0.0, superviewSize.width - CGRectGetMinX([self rectOfColumn:count]));
if (newSize > 0)
{
newSize = MAX([columnToResize minWidth], newSize);
newSize = MIN([columnToResize maxWidth], newSize);
[columnToResize setWidth:newSize];
}
}
[self setNeedsLayout];
}
- (void)noteNumberOfRowsChanged
{
_numberOfRows = [_dataSource numberOfRowsInTableView:self];
[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)
{
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] ? [CPTableView _defaultTableHeaderSortImage] : [CPTableView _defaultTableHeaderReverseSortImage];
[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)
[[aTableColumn headerView] _setIndicatorImage:anImage];
}
+ (CPImage)_defaultTableHeaderSortImage
{
return CPAppKitImage("tableview-headerview-ascending.png", CGSizeMake(9.0, 8.0));
}
+ (CPImage)_defaultTableHeaderReverseSortImage
{
return CPAppKitImage("tableview-headerview-descending.png", CGSizeMake(9.0, 8.0));
}
//Highlightable Column Headers
- (CPTableColumn)highlightedTableColumn
{
return _currentHighlightedTableColumn;
}
- (void)setHighlightedTableColumn:(CPTableColumn)aTableColumn
{
if (_currentHighlightedTableColumn == aTableColumn)
return;
if (_currentHighlightedTableColumn != nil)
[[_currentHighlightedTableColumn headerView] unsetThemeState:CPThemeStateHighlighted];
if (aTableColumn != nil)
[[aTableColumn headerView] setThemeState:CPThemeStateHighlighted];
_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:(CPPoint)dragViewOffset
{
var bounds = [self bounds],
view = [[CPView alloc] initWithFrame:bounds];
[view setBackgroundColor:[CPColor clearColor]];
[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 firstExposedColumn = [_exposedColumns firstIndex],
firstExposedRow = [_exposedRows firstIndex],
exposedColumnsLength = [_exposedColumns lastIndex] - firstExposedColumn + 1,
exposedRowsLength = [_exposedRows lastIndex] - firstExposedRow + 1,
columns = [],
rows = [];
[_exposedColumns getIndexes:columns maxCount:-1 inIndexRange:CPMakeRange(firstExposedColumn, exposedColumnsLength)];
[theDraggedRows getIndexes:rows maxCount:-1 inIndexRange:CPMakeRange(firstExposedRow, exposedRowsLength)];
var columnIndex = [columns count];
while (columnIndex--)
{
var column = columns[columnIndex],
tableColumn = [_tableColumns objectAtIndex:column],
rowIndex = [rows count];
while (rowIndex--)
{
var row = rows[rowIndex];
var dataView = [self _newDataViewForRow:row tableColumn:tableColumn];
[dataView setBackgroundColor:[CPColor clearColor]];
[dataView setFrame:[self frameOfDataViewAtColumn:column row:row]];
[dataView setObjectValue:[self _objectValueForTableColumn:tableColumn row:row]];
[view addSubview:dataView];
}
}
var dragPoint = [self convertPoint:[theDragEvent locationInWindow] fromView:nil];
dragViewOffset.x = CGRectGetWidth(bounds)/2 - dragPoint.x;
dragViewOffset.y = CGRectGetHeight(bounds)/2 - dragPoint.y;
return view;
}
- (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 < 0 && operation === CPTableViewDropAbove)
row = 0;
if(row >= [self numberOfRows] && operation === CPTableViewDropOn)
[CPException raise:CPInvalidArgumentException
reason:@"Attempt to set dropRow="+ row +", dropOperation=CPTableViewDropOn when [0 - "+
[self numberOfRows] +"] is valid range of rows."];
_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];
if (objectValue === undefined)
{
objectValue = [_dataSource tableView:self objectValueForTableColumn:aTableColumn row:aRowIndex];
tableColumnObjectValues[aRowIndex] = objectValue;
}
return objectValue;
}
- (CGRect)_exposedRect
{
var superview = [self superview];
if (![superview isKindOfClass:[CPClipView class]])
return [self bounds];
return [self convertRect:CGRectIntersection([superview bounds], [self frame]) fromView:superview];
}
- (void)load
{
if (_reloadAllRows)
{
[self _unloadDataViewsInRows:_exposedRows columns:_exposedColumns];
_exposedRows = [CPIndexSet indexSet];
_exposedColumns = [CPIndexSet indexSet];
_reloadAllRows = NO;
}
var exposedRect = [self _exposedRect],
exposedRows = [CPIndexSet indexSetWithIndexesInRange:[self rowsInRect:exposedRect]],
exposedColumns = [self columnIndexesInRect:exposedRect],
obscuredRows = [_exposedRows copy],
obscuredColumns = [_exposedColumns copy];
[obscuredRows removeIndexes:exposedRows];
[obscuredColumns removeIndexes:exposedColumns];
var newlyExposedRows = [exposedRows copy],
newlyExposedColumns = [exposedColumns copy];
[newlyExposedRows removeIndexes:_exposedRows];
[newlyExposedColumns removeIndexes:_exposedColumns];
var previouslyExposedRows = [exposedRows copy],
previouslyExposedColumns = [exposedColumns copy];
[previouslyExposedRows removeIndexes:newlyExposedRows];
[previouslyExposedColumns removeIndexes:newlyExposedColumns];
[self _unloadDataViewsInRows:previouslyExposedRows columns:obscuredColumns];
[self _unloadDataViewsInRows:obscuredRows columns:previouslyExposedColumns];
[self _unloadDataViewsInRows:obscuredRows columns:obscuredColumns];
[self _loadDataViewsInRows:previouslyExposedRows columns:newlyExposedColumns];
[self _loadDataViewsInRows:newlyExposedRows columns:previouslyExposedColumns];
[self _loadDataViewsInRows:newlyExposedRows columns:newlyExposedColumns];
_exposedRows = exposedRows;
_exposedColumns = exposedColumns;
[_tableDrawView setFrame:exposedRect];
[_tableDrawView display];
// Now clear all the leftovers
// FIXME: this could be faster!
for (identifier in _cachedDataViews)
{
var dataViews = _cachedDataViews[identifier],
count = dataViews.length;
while (count--)
[dataViews[count] removeFromSuperview];
}
}
- (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],
dataView = _dataViewsForTableColumns[tableColumnUID][row];
_dataViewsForTableColumns[tableColumnUID][row] = 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],
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],
isTextField = [dataView isKindOfClass:[CPTextField class]];
[dataView setFrame:[self frameOfDataViewAtColumn:column row:row]];
[dataView setObjectValue:[self _objectValueForTableColumn:tableColumn row:row]];
if (isColumnSelected || [self isRowSelected:row])
[dataView setThemeState:CPThemeStateHighlighted];
else
[dataView unsetThemeState:CPThemeStateHighlighted];
if (_implementedDelegateMethods & CPTableViewDelegate_tableView_willDisplayView_forTableColumn_row_)
[[self delegate] tableView:self willDisplayView:dataView forTableColumn:tableColumn row:row];
if ([dataView superview] !== self)
[self addSubview:dataView];
_dataViewsForTableColumns[tableColumnUID][row] = dataView;
if (_editingCellIndex && _editingCellIndex.x === column && _editingCellIndex.y === row) {
_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:(CPTextView)sender
{
[_dataSource tableView:self
setObjectValue:[sender objectValue]
forTableColumn:sender.tableViewEditedColumnObj
row:sender.tableViewEditedRowIndex];
}
- (CPView)_newDataViewForRow:(CPInteger)aRow tableColumn:(CPTableColumn)aTableColumn
{
return [aTableColumn _newDataViewForRow:aRow];
}
- (void)_enqueueReusableDataView:(CPView)aDataView
{
// 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]))];
}
- (CGRect)exposedClipRect
{
var superview = [self superview];
if (![superview isKindOfClass:[CPClipView class]])
return [self bounds];
return [self convertRect:CGRectIntersection([superview bounds], [self frame]) fromView:superview];
}
- (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 _exposedRect];
[self drawBackgroundInClipRect:exposedRect];
[self drawGridInClipRect:exposedRect];
[self highlightSelectionInClipRect:exposedRect];
}
- (void)drawBackgroundInClipRect:(CGRect)aRect
{
if (![self 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.0,
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, _gridColor);
CGContextStrokePath(context);
}
- (void)highlightSelectionInClipRect:(CGRect)aRect
{
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);
var deltaHeight = 0.5 * (_gridStyleMask & CPTableViewSolidHorizontalGridLineMask);
CGContextBeginPath(context);
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, _sourceListActiveGradient, rowRect.origin, CGPointMake(minX, maxY), 0);
CGContextClosePath(context);
CGContextBeginPath(context);
CGContextMoveToPoint(context, minX, minY);
CGContextAddLineToPoint(context, maxX, minY);
CGContextClosePath(context);
CGContextSetStrokeColor(context, _sourceListActiveTopLineColor);
CGContextStrokePath(context);
CGContextBeginPath(context);
CGContextMoveToPoint(context, minX, maxY);
CGContextAddLineToPoint(context, maxX, maxY - 1);
CGContextClosePath(context);
CGContextSetStrokeColor(context, _sourceListActiveBottomLineColor);
CGContextStrokePath(context);
}
}
CGContextClosePath(context);
if (!drawGradient)
{
[[CPColor selectionColor] 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++)
{
//console.log(columnIndexes);
var colRect = [self rectOfColumn:exposedColumnIndexes[c]],
colX = CGRectGetMaxX(colRect) + 0.5;
//console.log(colX);
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, [CPColor colorWithHexString:@"e5e5e5"]);
CGContextStrokePath(context);
}
- (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 (!(_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])
[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 ((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],
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;
}
}
else if (ABS(_startTrackingPoint.x - aPoint.x) < 5 && ABS(_startTrackingPoint.y - aPoint.y) < 5)
return YES;
}
_isSelectingSession = YES;
[self _updateSelectionWithMouseAtRow:row];
[self _updateSelectionWithMouseAtRow:[self rowAtPoint:aPoint]];
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)
{
_editingCellIndex = CGPointMake(columnIndex, rowIndex);
[self reloadDataForRowIndexes:[CPIndexSet indexSetWithIndex:rowIndex]
columnIndexes:[CPIndexSet indexSetWithIndex:columnIndex]];
return;
}
}
}
}
} //end of editing conditional
//double click actions
if([[CPApp currentEvent] clickCount] === 2 && _doubleAction && _target)
[self sendAction:_doubleAction to:_target];
}
/*!
@ignore
*/
- (CPDragOperation)draggingEntered:(id)sender
{
var dropOperation = [self _proposedDropOperation],
draggingLocation = [sender draggingLocation],
row;
var location = [self convertPoint:draggingLocation fromView:nil];
row = [self _proposedRowAtPoint:location];
if(_retargetedDropRow !== nil)
row = _retargetedDropRow;
var draggedTypes = [self registeredDraggedTypes],
count = [draggedTypes count],
i;
for (i = 0; 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 setHidden:YES];
}
/*
@ignore
*/
- (void)draggingEnded:(id)sender
{
[self _draggingEnded];
}
- (void)_draggingEnded
{
_retargetedDropOperation = nil;
_retargetedDropRow = nil;
_draggedRowIndexes = [CPIndexSet indexSet];
[_dropOperationFeedbackView setHidden:YES];
}
/*
@ignore
*/
- (BOOL)wantsPeriodicDraggingUpdates
{
return YES;
}
/*
@ignore
*/
- (CPTableViewDropOperation)_proposedDropOperation
{
//check is something is forced...
// otherwise we use the above action by default
if(_retargetedDropOperation !== nil)
return _retargetedDropOperation;
else
return CPTableViewDropAbove;
}
/*
@ignore
*/
- (CPInteger)_proposedRowAtPoint:(CGPoint)dragPoint
{
var numberOfRows = [self numberOfRows],
row;
// cocoa seems to jump to the next row when we approach the below row
dragPoint.y += FLOOR(_rowHeight/4);
if (dragPoint.y > numberOfRows * (_rowHeight + _intercellSpacing.height))
{
if ([self _proposedDropOperation] === CPTableViewDropAbove)
row = numberOfRows;
else
row = numberOfRows - 1;
}
else
row = [self rowAtPoint:dragPoint];
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;
}
- (CPDragOperation)draggingUpdated:(id)sender
{
var dropOperation = [self _proposedDropOperation],
numberOfRows = [self numberOfRows],
draggingLocation = [sender draggingLocation],
dragOperation,
row;
var location = [self convertPoint:draggingLocation fromView:nil];
row = [self _proposedRowAtPoint:location];
dragOperation = [self _validateDrop:sender proposedRow:row proposedDropOperation:dropOperation];
if(_retargetedDropRow !== nil)
row = _retargetedDropRow;
//if the user forces -1 then we should highlight the whole tabelview
var rowRect;
if(_retargetedDropRow === -1)
rowRect = [self exposedClipRect];
else
rowRect = [self rectOfRow:row];
var exposedClipRect = [self exposedClipRect];
var visibleWidth = _CGRectGetWidth(exposedClipRect);
rowRect = _CGRectMake(_CGRectGetMinX(exposedClipRect), rowRect.origin.y, visibleWidth, rowRect.size.height);
[_dropOperationFeedbackView setDropOperation:dropOperation];
[_dropOperationFeedbackView setHidden:(dragOperation == CPDragOperationNone)];
[_dropOperationFeedbackView setFrame:rowRect];
[_dropOperationFeedbackView setCurrentRow:row];
[self addSubview:_dropOperationFeedbackView];
// FIXME : Maybe we should do this in a timer outside this method.
// Problem: we don't know when the scroll ends or when the next -draggingUpdated is called.
if (row > 0 && location.y - CGRectGetMinY(exposedClipRect) < _rowHeight)
[self scrollRowToVisible:row - 1];
else if (row < numberOfRows && CGRectGetMaxY(exposedClipRect) - location.y < _rowHeight)
[self scrollRowToVisible:row + 1];
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 setHidden:YES];
return (_implementedDataSourceMethods & CPTableViewDataSource_tableView_validateDrop_proposedRow_proposedDropOperation_);
}
/*
@ignore
*/
- (BOOL)performDragOperation:(id)sender
{
var operation = [self _proposedDropOperation],
draggingLocation = [sender draggingLocation];
var location = [self convertPoint:draggingLocation fromView:nil];
if(_retargetedDropRow !== nil)
var row = _retargetedDropRow;
else
var row = [self rowAtPoint:location] - 1;
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];
}
}
_lastSelectedRow = ([newSelection count] > 0) ? aRow : -1;
// 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:[CPArray arrayWithObject: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)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)deleteBackward:(id)sender
{
if([_delegate respondsToSelector: @selector(tableViewDeleteKeyPressed:)])
[_delegate tableViewDeleteKeyPressed:self];
}
@end
var CPTableViewDataSourceKey = @"CPTableViewDataSourceKey",
CPTableViewDelegateKey = @"CPTableViewDelegateKey",
CPTableViewHeaderViewKey = @"CPTableViewHeaderViewKey",
CPTableViewTableColumnsKey = @"CPTableViewTableColumnsKey",
CPTableViewRowHeightKey = @"CPTableViewRowHeightKey",
CPTableViewIntercellSpacingKey = @"CPTableViewIntercellSpacingKey",
CPTableViewMultipleSelectionKey = @"CPTableViewMultipleSelectionKey",
CPTableViewEmptySelectionKey = @"CPTableViewEmptySelectionKey",
CPTableViewColumnReorderingKey = @"CPTableViewColumnReorderingKey",
CPTableViewColumnResizingKey = @"CPTableViewColumnResizingKey",
CPTableViewColumnSelectionKey = @"CPTableViewColumnSelectionKey",
CPTableViewGridColorKey = @"CPTableViewGridColorKey",
CPTableViewGridStyleMaskKey = @"CPTableViewGridStyleMaskKey",
CPTableViewUsesAlternatingBackgroundKey = @"CPTableViewUsesAlternatingBackgroundKey",
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];
_tableViewFlags = 0;
//Setting Display Attributes
_selectionHighlightStyle = CPTableViewSelectionHighlightStyleRegular;
_usesAlternatingRowBackgroundColors = [aCoder decodeBoolForKey:CPTableViewUsesAlternatingBackgroundKey];
[self setAlternatingRowBackgroundColors:[[CPColor whiteColor], [CPColor colorWithHexString:@"e4e7ff"]]];
_tableColumns = [aCoder decodeObjectForKey:CPTableViewTableColumnsKey];
[_tableColumns makeObjectsPerformSelector:@selector(setTableView:) withObject:self];
_tableColumnRanges = [];
_dirtyTableColumnRangeIndex = 0;
_numberOfHiddenColumns = 0;
_objectValues = { };
_dataViewsForTableColumns = { };
_dataViews= [];
_numberOfRows = 0;
_exposedRows = [CPIndexSet indexSet];
_exposedColumns = [CPIndexSet indexSet];
_cachedDataViews = { };
_rowHeight = [aCoder decodeFloatForKey:CPTableViewRowHeightKey];
if ([aCoder containsValueForKey:CPTableViewIntercellSpacingKey])
_intercellSpacing = [aCoder decodeSizeForKey:CPTableViewIntercellSpacingKey];
else
_intercellSpacing = _CGSizeMake(0.0, 0.0);
_gridColor = [aCoder decodeObjectForKey:CPTableViewGridColorKey] || [CPColor grayColor];
_gridStyleMask = [aCoder decodeIntForKey:CPTableViewGridStyleMaskKey] || CPTableViewGridNone;
_headerView = [aCoder decodeObjectForKey:CPTableViewHeaderViewKey];
_cornerView = [aCoder decodeObjectForKey:CPTableViewCornerViewKey];
_selectedColumnIndexes = [CPIndexSet indexSet];
_selectedRowIndexes = [CPIndexSet indexSet];
[self setDataSource:[aCoder decodeObjectForKey:CPTableViewDataSourceKey]];
[self setDelegate:[aCoder decodeObjectForKey:CPTableViewDelegateKey]];
_tableDrawView = [[_CPTableDrawView alloc] initWithTableView:self];
[_tableDrawView setBackgroundColor:[CPColor clearColor]];
[self addSubview:_tableDrawView];
[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 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:_gridColor forKey:CPTableViewGridColorKey];
[aCoder encodeInt:_gridStyleMask forKey:CPTableViewGridStyleMaskKey];
[aCoder encodeBool:_usesAlternatingRowBackgroundColors forKey:CPTableViewUsesAlternatingBackgroundKey];
[aCoder encodeObject:_cornerView forKey:CPTableViewCornerViewKey];
[aCoder encodeObject:_headerView forKey:CPTableViewHeaderViewKey];
}
@end
@implementation CPColor (tableview)
+ (CPColor)selectionColor
{
return [CPColor colorWithHexString:@"5f83b9"];
}
+ (CPColor)selectionColorSourceView
{
return [CPColor colorWithPatternImage:[[CPImage alloc] initByReferencingFile:@"Resources/tableviewselection.png" size:CGSizeMake(6,22)]];
}
@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 _dropOperationDrawingView : CPView
{
unsigned dropOperation @accessors;
CPTableView tableView @accessors;
int currentRow @accessors;
}
- (void)drawRect:(CGRect)aRect
{
if(tableView._destinationDragStyle === CPTableViewDraggingDestinationFeedbackStyleNone)
return;
var context = [[CPGraphicsContext currentContext] graphicsPort];
CGContextSetStrokeColor(context, [CPColor colorWithHexString:@"4886ca"]);
CGContextSetLineWidth(context, 3);
if(dropOperation === CPTableViewDropOn)
{
//if row is selected don't fill and stroke white
var selectedRows = [tableView selectedRowIndexes];
var 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);
}
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]);
}
}
@end