API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPTableView.j
Go to the documentation of this file.
1 /*
2  * CPTableView.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2009, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
25 
26 
27 CPTableViewColumnDidMoveNotification = @"CPTableViewColumnDidMoveNotification";
28 CPTableViewColumnDidResizeNotification = @"CPTableViewColumnDidResizeNotification";
29 CPTableViewSelectionDidChangeNotification = @"CPTableViewSelectionDidChangeNotification";
30 CPTableViewSelectionIsChangingNotification = @"CPTableViewSelectionIsChangingNotification";
31 
39 
41 
63 
64 //CPTableViewDraggingDestinationFeedbackStyles
68 
69 //CPTableViewDropOperations
72 
73 CPSourceListGradient = "CPSourceListGradient";
74 CPSourceListTopLineColor = "CPSourceListTopLineColor";
75 CPSourceListBottomLineColor = "CPSourceListBottomLineColor";
76 
77 // TODO: add docs
78 
82 
86 
88 CPTableViewUniformColumnAutoresizingStyle = 1; // FIX ME: This is FUBAR
93 
94 #define NUMBER_OF_COLUMNS() (_tableColumns.length)
95 #define UPDATE_COLUMN_RANGES_IF_NECESSARY() if (_dirtyTableColumnRangeIndex !== CPNotFound) [self _recalculateTableColumnRanges];
96 
97 
98 @implementation _CPTableDrawView : CPView
99 {
100  CPTableView _tableView;
101 }
102 
103 - (id)initWithTableView:(CPTableView)aTableView
104 {
105  self = [super init];
106 
107  if (self)
108  _tableView = aTableView;
109 
110  return self;
111 }
112 
113 - (void)drawRect:(CGRect)aRect
114 {
115  var frame = [self frame],
117 
118  CGContextTranslateCTM(context, -_CGRectGetMinX(frame), -_CGRectGetMinY(frame));
119 
120  [_tableView _drawRect:aRect];
121 }
122 
123 @end
124 
148 @implementation CPTableView : CPControl
149 {
150  id _dataSource;
151  CPInteger _implementedDataSourceMethods;
152 
153  id _delegate;
154  CPInteger _implementedDelegateMethods;
155 
156  CPArray _tableColumns;
157  CPArray _tableColumnRanges;
158  CPInteger _dirtyTableColumnRangeIndex;
159  CPInteger _numberOfHiddenColumns;
160 
161  BOOL _reloadAllRows;
162  Object _objectValues;
163 
164  CGRect _exposedRect;
165  CPIndexSet _exposedRows;
166  CPIndexSet _exposedColumns;
167 
168  Object _dataViewsForTableColumns;
169  Object _cachedDataViews;
170 
171  //Configuring Behavior
172  BOOL _allowsColumnReordering;
173  BOOL _allowsColumnResizing;
174  BOOL _allowsColumnSelection;
175  BOOL _allowsMultipleSelection;
176  BOOL _allowsEmptySelection;
177 
178  CPArray _sortDescriptors;
179 
180  //Setting Display Attributes
181  CGSize _intercellSpacing;
182  float _rowHeight;
183 
184  BOOL _usesAlternatingRowBackgroundColors;
185  CPArray _alternatingRowBackgroundColors;
186 
187  unsigned _selectionHighlightStyle;
188  CPTableColumn _currentHighlightedTableColumn;
189  unsigned _gridStyleMask;
190 
191  unsigned _numberOfRows;
192  CPIndexSet _groupRows;
193 
194  CPArray _cachedRowHeights;
195 
196  // Persistence
197  CPString _autosaveName;
198  BOOL _autosaveTableColumns;
199 
200  CPTableHeaderView _headerView;
201  _CPCornerView _cornerView;
202 
203  CPIndexSet _selectedColumnIndexes;
204  CPIndexSet _selectedRowIndexes;
205  CPInteger _selectionAnchorRow;
206  CPInteger _lastSelectedRow;
207  CPIndexSet _previouslySelectedRowIndexes;
208  CGPoint _startTrackingPoint;
209  CPDate _startTrackingTimestamp;
210  BOOL _trackingPointMovedOutOfClickSlop;
211  CGPoint _editingCellIndex;
212 
213  _CPTableDrawView _tableDrawView;
214 
215  SEL _doubleAction;
216  CPInteger _clickedRow;
217  CPInteger _clickedColumn;
218  unsigned _columnAutoResizingStyle;
219 
220  int _lastTrackedRowIndex;
221  CGPoint _originalMouseDownPoint;
222  BOOL _verticalMotionCanDrag;
223  unsigned _destinationDragStyle;
224  BOOL _isSelectingSession;
225  CPIndexSet _draggedRowIndexes;
226  BOOL _wasSelectionBroken;
227 
228  _CPDropOperationDrawingView _dropOperationFeedbackView;
229  CPDragOperation _dragOperationDefaultMask;
230  int _retargetedDropRow;
231  CPDragOperation _retargetedDropOperation;
232 
233  BOOL _disableAutomaticResizing;
234  BOOL _lastColumnShouldSnap;
235  BOOL _implementsCustomDrawRow;
236  BOOL _contentBindingExpicitelySet;
237 
238  CPTableColumn _draggedColumn;
239  CPArray _differedColumnDataToRemove;
240 }
241 
245 + (CPString)defaultThemeClass
246 {
247  return @"tableview";
248 }
249 
253 + (id)themeAttributes
254 {
256  forKeys:["alternating-row-colors", "grid-color", "highlighted-grid-color", "selection-color", "sourcelist-selection-color", "sort-image", "sort-image-reversed", "selection-radius"]];
257 }
258 
259 - (id)initWithFrame:(CGRect)aFrame
260 {
261  self = [super initWithFrame:aFrame];
262 
263  if (self)
264  {
265  //Configuring Behavior
266  _allowsColumnReordering = YES;
267  _allowsColumnResizing = YES;
268  _allowsMultipleSelection = NO;
269  _allowsEmptySelection = YES;
270  _allowsColumnSelection = NO;
271  _disableAutomaticResizing = NO;
272 
273  //Setting Display Attributes
274  _selectionHighlightStyle = CPTableViewSelectionHighlightStyleRegular;
275 
278  [[CPColor whiteColor], [CPColor colorWithRed:245.0 / 255.0 green:249.0 / 255.0 blue:252.0 / 255.0 alpha:1.0]]];
279 
280  _tableColumns = [];
281  _tableColumnRanges = [];
282  _dirtyTableColumnRangeIndex = CPNotFound;
283  _numberOfHiddenColumns = 0;
284 
285  _intercellSpacing = _CGSizeMake(3.0, 2.0);
286  _rowHeight = 23.0;
287 
288  [self setGridColor:[CPColor colorWithHexString:@"dce0e2"]];
289  [self setGridStyleMask:CPTableViewGridNone];
290 
291  [self setHeaderView:[[CPTableHeaderView alloc] initWithFrame:_CGRectMake(0, 0, [self bounds].size.width, _rowHeight)]];
292  [self setCornerView:[[_CPCornerView alloc] initWithFrame:_CGRectMake(0, 0, [CPScroller scrollerWidth], _CGRectGetHeight([_headerView frame]))]];
293 
294  _currentHighlightedTableColumn = nil;
295 
296  _draggedRowIndexes = [CPIndexSet indexSet];
297  _verticalMotionCanDrag = YES;
298  _isSelectingSession = NO;
299  _retargetedDropRow = nil;
300  _retargetedDropOperation = nil;
301  _dragOperationDefaultMask = nil;
303  _contentBindingExpicitelySet = NO;
304 
306  [self _init];
307  }
308 
309  return self;
310 }
311 
312 
318 - (void)_init
319 {
320  _lastSelectedRow = _clickedColumn = _clickedRow = -1;
321 
322  _selectedColumnIndexes = [CPIndexSet indexSet];
323  _selectedRowIndexes = [CPIndexSet indexSet];
324 
325  _dropOperationFeedbackView = [[_CPDropOperationDrawingView alloc] initWithFrame:_CGRectMakeZero()];
326  [_dropOperationFeedbackView setTableView:self];
327 
328  _lastColumnShouldSnap = NO;
329 
330  if (!_alternatingRowBackgroundColors)
331  _alternatingRowBackgroundColors = [[CPColor whiteColor], [CPColor colorWithHexString:@"e4e7ff"]];
332 
333  _tableColumnRanges = [];
334  _dirtyTableColumnRangeIndex = 0;
335  _numberOfHiddenColumns = 0;
336 
337  _objectValues = { };
338  _dataViewsForTableColumns = { };
339  _numberOfRows = 0;
340  _exposedRows = [CPIndexSet indexSet];
341  _exposedColumns = [CPIndexSet indexSet];
342  _cachedDataViews = { };
343 
344  _cachedRowHeights = [];
345 
346  _groupRows = [CPIndexSet indexSet];
347 
348  _tableDrawView = [[_CPTableDrawView alloc] initWithTableView:self];
349  [_tableDrawView setBackgroundColor:[CPColor clearColor]];
350  [self addSubview:_tableDrawView];
351 
352  _draggedColumn = nil;
353 
354 /* //gradients for the source list when CPTableView is NOT first responder or the window is NOT key
355  // FIX ME: we need to actually implement this.
356  _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);
357  _sourceListInactiveTopLineColor = [CPColor colorWithCalibratedRed:(173.0/255.0) green:(187.0/255.0) blue:(209.0/255.0) alpha:1.0];
358  _sourceListInactiveBottomLineColor = [CPColor colorWithCalibratedRed:(150.0/255.0) green:(161.0/255.0) blue:(183.0/255.0) alpha:1.0];*/
359  _differedColumnDataToRemove = [];
360  _implementsCustomDrawRow = [self implementsSelector:@selector(drawRow:clipRect:)];
361 
362  if (!_sortDescriptors)
363  _sortDescriptors = [];
364 }
365 
431 - (void)setDataSource:(id)aDataSource
432 {
433  if (_dataSource === aDataSource)
434  return;
435 
436  _dataSource = aDataSource;
437  _implementedDataSourceMethods = 0;
438 
439  if (!_dataSource)
440  return;
441 
442  var hasContentBinding = !![self infoForBinding:@"content"];
443 
444  if ([_dataSource respondsToSelector:@selector(numberOfRowsInTableView:)])
445  _implementedDataSourceMethods |= CPTableViewDataSource_numberOfRowsInTableView_;
446 
447  if ([_dataSource respondsToSelector:@selector(tableView:objectValueForTableColumn:row:)])
449 
450  if ([_dataSource respondsToSelector:@selector(tableView:setObjectValue:forTableColumn:row:)])
452 
453  if ([_dataSource respondsToSelector:@selector(tableView:acceptDrop:row:dropOperation:)])
454  _implementedDataSourceMethods |= CPTableViewDataSource_tableView_acceptDrop_row_dropOperation_;
455 
456  if ([_dataSource respondsToSelector:@selector(tableView:namesOfPromisedFilesDroppedAtDestination:forDraggedRowsWithIndexes:)])
458 
459  if ([_dataSource respondsToSelector:@selector(tableView:validateDrop:proposedRow:proposedDropOperation:)])
461 
462  if ([_dataSource respondsToSelector:@selector(tableView:writeRowsWithIndexes:toPasteboard:)])
464 
465  if ([_dataSource respondsToSelector:@selector(tableView:sortDescriptorsDidChange:)])
466  _implementedDataSourceMethods |= CPTableViewDataSource_tableView_sortDescriptorsDidChange_;
467 
468  [self reloadData];
469 }
470 
474 - (id)dataSource
475 {
476  return _dataSource;
477 }
478 
479 //Loading Data
480 
486 - (void)reloadDataForRowIndexes:(CPIndexSet)rowIndexes columnIndexes:(CPIndexSet)columnIndexes
487 {
488  [self reloadData];
489 // [_previouslyExposedRows removeIndexes:rowIndexes];
490 // [_previouslyExposedColumns removeIndexes:columnIndexes];
491 }
492 
496 - (void)reloadData
497 {
498  //if (!_dataSource)
499  // return;
500 
501  _reloadAllRows = YES;
502  _objectValues = { };
503  _cachedRowHeights = [];
504 
505  // Otherwise, if we have a row marked as group with a
506  // index greater than the new number or rows
507  // it keeps the the graphical group style.
508  [_groupRows removeAllIndexes];
509 
510  // This updates the size too.
512 
513  [self setNeedsLayout];
514  [self setNeedsDisplay:YES];
515 }
516 
517 //Target-action Behavior
524 - (void)setDoubleAction:(SEL)anAction
525 {
526  _doubleAction = anAction;
527 }
528 
532 - (SEL)doubleAction
533 {
534  return _doubleAction;
535 }
536 
537 /*
538  Returns the index of the the column the user clicked to trigger an action, or -1 if no column was clicked.
539 */
540 - (CPInteger)clickedColumn
541 {
542  return _clickedColumn;
543 }
544 
548 - (CPInteger)clickedRow
549 {
550  return _clickedRow;
551 }
552 
553 //Configuring Behavior
554 
558 - (void)setAllowsColumnReordering:(BOOL)shouldAllowColumnReordering
559 {
560  _allowsColumnReordering = !!shouldAllowColumnReordering;
561 }
562 
566 - (BOOL)allowsColumnReordering
567 {
568  return _allowsColumnReordering;
569 }
570 
575 - (void)setAllowsColumnResizing:(BOOL)shouldAllowColumnResizing
576 {
577  _allowsColumnResizing = !!shouldAllowColumnResizing;
578 }
579 
583 - (BOOL)allowsColumnResizing
584 {
585  return _allowsColumnResizing;
586 }
587 
592 - (void)setAllowsMultipleSelection:(BOOL)shouldAllowMultipleSelection
593 {
594  _allowsMultipleSelection = !!shouldAllowMultipleSelection;
595 }
596 
602 - (BOOL)allowsMultipleSelection
603 {
604  return _allowsMultipleSelection;
605 }
606 
611 - (void)setAllowsEmptySelection:(BOOL)shouldAllowEmptySelection
612 {
613  _allowsEmptySelection = !!shouldAllowEmptySelection;
614 }
615 
619 - (BOOL)allowsEmptySelection
620 {
621  return _allowsEmptySelection;
622 }
623 
629 - (void)setAllowsColumnSelection:(BOOL)shouldAllowColumnSelection
630 {
631  _allowsColumnSelection = !!shouldAllowColumnSelection;
632 }
633 
634 
638 - (BOOL)allowsColumnSelection
639 {
640  return _allowsColumnSelection;
641 }
642 
643 //Setting Display Attributes
650 - (void)setIntercellSpacing:(CGSize)aSize
651 {
652  if (_CGSizeEqualToSize(_intercellSpacing, aSize))
653  return;
654 
655  _intercellSpacing = _CGSizeMakeCopy(aSize);
656 
657  _dirtyTableColumnRangeIndex = 0; // so that _recalculateTableColumnRanges will work
658  [self _recalculateTableColumnRanges];
659 
660  [self setNeedsLayout];
661  [_headerView setNeedsDisplay:YES];
662  [_headerView setNeedsLayout];
663 
664  [self reloadData];
665 }
666 
670 - (CGSize)intercellSpacing
671 {
672  return _CGSizeMakeCopy(_intercellSpacing);
673 }
674 
681 - (void)setRowHeight:(unsigned)aRowHeight
682 {
683  // Accept row heights such as "0".
684  aRowHeight = +aRowHeight;
685 
686  if (_rowHeight === aRowHeight)
687  return;
688 
689  _rowHeight = MAX(0.0, aRowHeight);
690 
691  [self setNeedsLayout];
692 }
693 
697 - (unsigned)rowHeight
698 {
699  return _rowHeight;
700 }
701 
707 - (void)setUsesAlternatingRowBackgroundColors:(BOOL)shouldUseAlternatingRowBackgroundColors
708 {
709  _usesAlternatingRowBackgroundColors = shouldUseAlternatingRowBackgroundColors;
710 }
711 
715 - (BOOL)usesAlternatingRowBackgroundColors
716 {
717  return _usesAlternatingRowBackgroundColors;
718 }
719 
725 - (void)setAlternatingRowBackgroundColors:(CPArray)alternatingRowBackgroundColors
726 {
727  [self setValue:alternatingRowBackgroundColors forThemeAttribute:@"alternating-row-colors"];
728 
729  [self setNeedsDisplay:YES];
730 }
731 
735 - (CPArray)alternatingRowBackgroundColors
736 {
737  return [self currentValueForThemeAttribute:@"alternating-row-colors"];
738 }
739 
751 - (unsigned)selectionHighlightStyle
752 {
753  return _selectionHighlightStyle;
754 }
755 
767 - (void)setSelectionHighlightStyle:(unsigned)aSelectionHighlightStyle
768 {
769  _selectionHighlightStyle = aSelectionHighlightStyle;
770  [self setNeedsDisplay:YES];
771 
772  if (aSelectionHighlightStyle === CPTableViewSelectionHighlightStyleSourceList)
774  else
776 }
777 
783 - (void)setSelectionHighlightColor:(CPColor)aColor
784 {
785  [self setValue:aColor forThemeAttribute:@"selection-color"];
786 
787  [self setNeedsDisplay:YES];
788 }
789 
793 - (CPColor)selectionHighlightColor
794 {
795  return [self currentValueForThemeAttribute:@"selection-color"];
796 }
797 
809 - (void)setSelectionGradientColors:(CPDictionary)aDictionary
810 {
811  [self setValue:aDictionary forThemeAttribute:@"sourcelist-selection-color"];
812 
813  [self setNeedsDisplay:YES];
814 }
815 
824 - (CPDictionary)selectionGradientColors
825 {
826  return [self currentValueForThemeAttribute:@"sourcelist-selection-color"];
827 }
828 
833 - (void)setGridColor:(CPColor)aColor
834 {
835  [self setValue:aColor forThemeAttribute:@"grid-color"];
836 
837  [self setNeedsDisplay:YES];
838 }
839 
843 - (CPColor)gridColor
844 {
845  return [self currentValueForThemeAttribute:@"grid-color"];;
846 }
847 
853 - (void)setGridStyleMask:(unsigned)aGrideStyleMask
854 {
855  if (_gridStyleMask === aGrideStyleMask)
856  return;
857 
858  _gridStyleMask = aGrideStyleMask;
859 
860  [self setNeedsDisplay:YES];
861 }
862 
866 - (unsigned)gridStyleMask
867 {
868  return _gridStyleMask;
869 }
870 
871 //Column Management
872 
877 - (void)addTableColumn:(CPTableColumn)aTableColumn
878 {
879  [_tableColumns addObject:aTableColumn];
880  [aTableColumn setTableView:self];
881 
882  if (_dirtyTableColumnRangeIndex < 0)
883  _dirtyTableColumnRangeIndex = NUMBER_OF_COLUMNS() - 1;
884  else
885  _dirtyTableColumnRangeIndex = MIN(NUMBER_OF_COLUMNS() - 1, _dirtyTableColumnRangeIndex);
886 
887  if ([[self sortDescriptors] count] > 0)
888  {
889  var mainSortDescriptor = [[self sortDescriptors] objectAtIndex:0];
890 
891  if (aTableColumn === [self _tableColumnForSortDescriptor:mainSortDescriptor])
892  {
893  var image = [mainSortDescriptor ascending] ? [self _tableHeaderSortImage] : [self _tableHeaderReverseSortImage];
894  [self setIndicatorImage:image inTableColumn:aTableColumn];
895  }
896  }
897 
898  [self tile];
899  [self setNeedsLayout];
900 }
901 
906 - (void)removeTableColumn:(CPTableColumn)aTableColumn
907 {
908  if ([aTableColumn tableView] !== self)
909  return;
910 
911  var index = [_tableColumns indexOfObjectIdenticalTo:aTableColumn];
912 
913  if (index === CPNotFound)
914  return;
915 
916  // we defer the actual removal until the end of the runloop in order to keep a reference to the column.
917  [_differedColumnDataToRemove addObject:{"column":aTableColumn, "shouldBeHidden": [aTableColumn isHidden]}];
918 
919  [aTableColumn setHidden:YES];
920  [aTableColumn setTableView:nil];
921 
922  var tableColumnUID = [aTableColumn UID];
923 
924  if (_objectValues[tableColumnUID])
925  _objectValues[tableColumnUID] = nil;
926 
927  if (_dirtyTableColumnRangeIndex < 0)
928  _dirtyTableColumnRangeIndex = index;
929  else
930  _dirtyTableColumnRangeIndex = MIN(index, _dirtyTableColumnRangeIndex);
931 
932  [self setNeedsLayout];
933 }
934 
939 - (void)_setDraggedColumn:(CPTableColumn)aColumn
940 {
941  if (_draggedColumn === aColumn)
942  return;
943 
944  _draggedColumn = aColumn;
945 
946  [self reloadDataForRowIndexes:_exposedRows columnIndexes:[CPIndexSet indexSetWithIndex:[_tableColumns indexOfObject:aColumn]]];
947 }
948 
949 /*
950  @ignore
951  Same as moveColumn:toColumn: but doesn't trigger an autosave
952 */
953 - (void)_moveColumn:(unsigned)fromIndex toColumn:(unsigned)toIndex
954 {
955  // Convert parameters such as "0" to 0.
956  fromIndex = +fromIndex;
957  toIndex = +toIndex;
958 
959  if (fromIndex === toIndex)
960  return;
961 
962  if (_dirtyTableColumnRangeIndex < 0)
963  _dirtyTableColumnRangeIndex = MIN(fromIndex, toIndex);
964  else
965  _dirtyTableColumnRangeIndex = MIN(fromIndex, toIndex, _dirtyTableColumnRangeIndex);
966 
967  var tableColumn = _tableColumns[fromIndex];
968 
969  [_tableColumns removeObjectAtIndex:fromIndex];
970  [_tableColumns insertObject:tableColumn atIndex:toIndex];
971 
972  [[self headerView] setNeedsLayout];
973  [[self headerView] setNeedsDisplay:YES];
974 
975  var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])],
976  columnIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(fromIndex, toIndex)];
977 
978  [self reloadDataForRowIndexes:rowIndexes columnIndexes:columnIndexes];
979 
980  // Notify even if programmatically moving a column as in Cocoa.
981  // TODO Only notify when a column drag operation ends, not each time a column reaches a new slot?
982  [[CPNotificationCenter defaultCenter] postNotificationName:CPTableViewColumnDidMoveNotification
983  object:self
984  userInfo:[CPDictionary dictionaryWithObjects:[fromIndex, toIndex]
985  forKeys:[@"CPOldColumn", @"CPNewColumn"]]];
986 }
987 
993 - (void)moveColumn:(int)theColumnIndex toColumn:(int)theToIndex
994 {
995  [self _moveColumn:theColumnIndex toColumn:theToIndex];
996  [self _autosave];
997 }
998 
1003 - (void)_tableColumnVisibilityDidChange:(CPTableColumn)aColumn
1004 {
1005  var columnIndex = [[self tableColumns] indexOfObjectIdenticalTo:aColumn];
1006 
1007  if (_dirtyTableColumnRangeIndex < 0)
1008  _dirtyTableColumnRangeIndex = columnIndex;
1009  else
1010  _dirtyTableColumnRangeIndex = MIN(columnIndex, _dirtyTableColumnRangeIndex);
1011 
1012  [[self headerView] setNeedsLayout];
1013  [[self headerView] setNeedsDisplay:YES];
1014 
1015  var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])];
1016  [self reloadDataForRowIndexes:rowIndexes columnIndexes:[CPIndexSet indexSetWithIndex:columnIndex]];
1017 }
1018 
1022 - (CPArray)tableColumns
1023 {
1024  return _tableColumns;
1025 }
1026 
1033 - (CPInteger)columnWithIdentifier:(CPString)anIdentifier
1034 {
1035  var index = 0,
1036  count = NUMBER_OF_COLUMNS();
1037 
1038  for (; index < count; ++index)
1039  if ([_tableColumns[index] identifier] === anIdentifier)
1040  return index;
1041 
1042  return CPNotFound;
1043 }
1044 
1051 - (CPTableColumn)tableColumnWithIdentifier:(CPString)anIdentifier
1052 {
1053  var index = [self columnWithIdentifier:anIdentifier];
1054 
1055  if (index === CPNotFound)
1056  return nil;
1057 
1058  return _tableColumns[index];
1059 }
1060 
1064 - (void)_didResizeTableColumn:(CPTableColumn)theColumn
1065 {
1066  [self _autosave];
1067 }
1068 
1069 //Selecting Columns and Rows
1070 
1077 - (void)selectColumnIndexes:(CPIndexSet)columns byExtendingSelection:(BOOL)shouldExtendSelection
1078 {
1079  // If we're out of range, just return
1080  if (([columns firstIndex] != CPNotFound && [columns firstIndex] < 0) || [columns lastIndex] >= [self numberOfColumns])
1081  return;
1082 
1083  // We deselect all rows when selecting columns.
1084  if ([_selectedRowIndexes count] > 0)
1085  {
1086  [self _updateHighlightWithOldRows:_selectedRowIndexes newRows:[CPIndexSet indexSet]];
1087  _selectedRowIndexes = [CPIndexSet indexSet];
1088  }
1089 
1090  var previousSelectedIndexes = [_selectedColumnIndexes copy];
1091 
1092  if (shouldExtendSelection)
1093  [_selectedColumnIndexes addIndexes:columns];
1094  else
1095  _selectedColumnIndexes = [columns copy];
1096 
1097  [self _updateHighlightWithOldColumns:previousSelectedIndexes newColumns:_selectedColumnIndexes];
1098  [self setNeedsDisplay:YES]; // FIXME: should be setNeedsDisplayInRect:enclosing rect of new (de)selected columns
1099  // but currently -drawRect: is not implemented here
1100  if (_headerView)
1101  [_headerView setNeedsDisplay:YES];
1102 
1103  [self _noteSelectionDidChange];
1104 }
1105 
1109 - (void)_setSelectedRowIndexes:(CPIndexSet)rows
1110 {
1111  if ([_selectedRowIndexes isEqualToIndexSet:rows])
1112  return;
1113 
1114  var previousSelectedIndexes = _selectedRowIndexes;
1115 
1116  _lastSelectedRow = ([rows count] > 0) ? [rows lastIndex] : -1;
1117  _selectedRowIndexes = [rows copy];
1118 
1119  [self _updateHighlightWithOldRows:previousSelectedIndexes newRows:_selectedRowIndexes];
1120  [self setNeedsDisplay:YES]; // FIXME: should be setNeedsDisplayInRect:enclosing rect of new (de)selected rows
1121  // but currently -drawRect: is not implemented here
1122 
1123  var binderClass = [[self class] _binderClassForBinding:@"selectionIndexes"];
1124  [[binderClass getBinding:@"selectionIndexes" forObject:self] reverseSetValueFor:@"selectedRowIndexes"];
1125 
1126  [self _noteSelectionDidChange];
1127 }
1128 
1135 - (void)selectRowIndexes:(CPIndexSet)rows byExtendingSelection:(BOOL)shouldExtendSelection
1136 {
1137  if ([rows isEqualToIndexSet:_selectedRowIndexes] ||
1138  (([rows firstIndex] != CPNotFound && [rows firstIndex] < 0) || [rows lastIndex] >= [self numberOfRows]))
1139  return;
1140 
1141  // We deselect all columns when selecting rows.
1142  if ([_selectedColumnIndexes count] > 0)
1143  {
1144  [self _updateHighlightWithOldColumns:_selectedColumnIndexes newColumns:[CPIndexSet indexSet]];
1145  _selectedColumnIndexes = [CPIndexSet indexSet];
1146  if (_headerView)
1147  [_headerView setNeedsDisplay:YES];
1148  }
1149 
1150  var newSelectedIndexes;
1151  if (shouldExtendSelection)
1152  {
1153  newSelectedIndexes = [_selectedRowIndexes copy];
1154  [newSelectedIndexes addIndexes:rows];
1155  }
1156  else
1157  newSelectedIndexes = [rows copy];
1158 
1159  [self _setSelectedRowIndexes:newSelectedIndexes];
1160 }
1161 
1165 - (void)_updateHighlightWithOldRows:(CPIndexSet)oldRows newRows:(CPIndexSet)newRows
1166 {
1167  var firstExposedRow = [_exposedRows firstIndex],
1168  exposedLength = [_exposedRows lastIndex] - firstExposedRow + 1,
1169  deselectRows = [],
1170  selectRows = [],
1171  deselectRowIndexes = [oldRows copy],
1172  selectRowIndexes = [newRows copy];
1173 
1174  [deselectRowIndexes removeMatches:selectRowIndexes];
1175  [deselectRowIndexes getIndexes:deselectRows maxCount:-1 inIndexRange:CPMakeRange(firstExposedRow, exposedLength)];
1176  [selectRowIndexes getIndexes:selectRows maxCount:-1 inIndexRange:CPMakeRange(firstExposedRow, exposedLength)];
1177 
1178  for (var identifier in _dataViewsForTableColumns)
1179  {
1180  var dataViewsInTableColumn = _dataViewsForTableColumns[identifier],
1181  count = deselectRows.length;
1182  while (count--)
1183  [self _performSelection:NO forRow:deselectRows[count] context:dataViewsInTableColumn];
1184 
1185  count = selectRows.length;
1186  while (count--)
1187  [self _performSelection:YES forRow:selectRows[count] context:dataViewsInTableColumn];
1188  }
1189 }
1190 
1194 - (void)_performSelection:(BOOL)select forRow:(CPInteger)rowIndex context:(id)context
1195 {
1196  var view = context[rowIndex],
1197  selector = select ? @"setThemeState:" : @"unsetThemeState:";
1198 
1199  [view performSelector:CPSelectorFromString(selector) withObject:CPThemeStateSelectedDataView];
1200 }
1201 
1205 - (void)_updateHighlightWithOldColumns:(CPIndexSet)oldColumns newColumns:(CPIndexSet)newColumns
1206 {
1207  var firstExposedColumn = [_exposedColumns firstIndex],
1208  exposedLength = [_exposedColumns lastIndex] - firstExposedColumn +1,
1209  deselectColumns = [],
1210  selectColumns = [],
1211  deselectColumnIndexes = [oldColumns copy],
1212  selectColumnIndexes = [newColumns copy],
1213  selectRows = [];
1214 
1215  [deselectColumnIndexes removeMatches:selectColumnIndexes];
1216  [deselectColumnIndexes getIndexes:deselectColumns maxCount:-1 inIndexRange:CPMakeRange(firstExposedColumn, exposedLength)];
1217  [selectColumnIndexes getIndexes:selectColumns maxCount:-1 inIndexRange:CPMakeRange(firstExposedColumn, exposedLength)];
1218  [_exposedRows getIndexes:selectRows maxCount:-1 inIndexRange:nil];
1219 
1220  var rowsCount = selectRows.length,
1221  count = deselectColumns.length;
1222  while (count--)
1223  {
1224  var columnIndex = deselectColumns[count],
1225  identifier = [_tableColumns[columnIndex] UID],
1226  dataViewsInTableColumn = _dataViewsForTableColumns[identifier];
1227 
1228  for (var i = 0; i < rowsCount; i++)
1229  {
1230  var rowIndex = selectRows[i],
1231  dataView = dataViewsInTableColumn[rowIndex];
1232  [dataView unsetThemeState:CPThemeStateSelectedDataView];
1233  }
1234 
1235  if (_headerView)
1236  {
1237  var headerView = [_tableColumns[columnIndex] headerView];
1238  [headerView unsetThemeState:CPThemeStateSelected];
1239  }
1240  }
1241 
1242  count = selectColumns.length;
1243  while (count--)
1244  {
1245  var columnIndex = selectColumns[count],
1246  identifier = [_tableColumns[columnIndex] UID],
1247  dataViewsInTableColumn = _dataViewsForTableColumns[identifier];
1248 
1249  for (var i = 0; i < rowsCount; i++)
1250  {
1251  var rowIndex = selectRows[i],
1252  dataView = dataViewsInTableColumn[rowIndex];
1253  [dataView setThemeState:CPThemeStateSelectedDataView];
1254  }
1255  if (_headerView)
1256  {
1257  var headerView = [_tableColumns[columnIndex] headerView];
1258  [headerView setThemeState:CPThemeStateSelected];
1259  }
1260  }
1261 }
1262 
1266 - (int)selectedColumn
1267 {
1268  return [_selectedColumnIndexes lastIndex];
1269 }
1270 
1274 - (CPIndexSet)selectedColumnIndexes
1275 {
1276  return _selectedColumnIndexes;
1277 }
1278 
1282 - (int)selectedRow
1283 {
1284  return _lastSelectedRow;
1285 }
1286 
1290 - (CPIndexSet)selectedRowIndexes
1291 {
1292  return [_selectedRowIndexes copy];
1293 }
1294 
1300 - (void)deselectColumn:(CPInteger)anIndex
1301 {
1302  var selectedColumnIndexes = [_selectedColumnIndexes copy];
1304  [self selectColumnIndexes:selectedColumnIndexes byExtendingSelection:NO];
1305  [self _noteSelectionDidChange];
1306 }
1307 
1313 - (void)deselectRow:(CPInteger)aRow
1314 {
1315  var selectedRowIndexes = [_selectedRowIndexes copy];
1317  [self selectRowIndexes:selectedRowIndexes byExtendingSelection:NO];
1318  [self _noteSelectionDidChange];
1319 }
1320 
1324 - (CPInteger)numberOfSelectedColumns
1325 {
1326  return [_selectedColumnIndexes count];
1327 }
1328 
1332 - (CPInteger)numberOfSelectedRows
1333 {
1334  return [_selectedRowIndexes count];
1335 }
1336 
1343 - (BOOL)isColumnSelected:(CPInteger)anIndex
1344 {
1345  return [_selectedColumnIndexes containsIndex:anIndex];
1346 }
1347 
1354 - (BOOL)isRowSelected:(CPInteger)aRow
1355 {
1356  return [_selectedRowIndexes containsIndex:aRow];
1357 }
1358 
1359 
1363 - (void)deselectAll
1364 {
1367 }
1368 
1372 - (int)numberOfColumns
1373 {
1374  return NUMBER_OF_COLUMNS();
1375 }
1376 
1380 - (int)numberOfRows
1381 {
1382  if (_numberOfRows !== nil)
1383  return _numberOfRows;
1384 
1385  var contentBindingInfo = [self infoForBinding:@"content"];
1386 
1387  if (contentBindingInfo)
1388  {
1389  var destination = [contentBindingInfo objectForKey:CPObservedObjectKey],
1390  keyPath = [contentBindingInfo objectForKey:CPObservedKeyPathKey];
1391 
1392  _numberOfRows = [[destination valueForKeyPath:keyPath] count];
1393  }
1394  else if (_dataSource && (_implementedDataSourceMethods & CPTableViewDataSource_numberOfRowsInTableView_))
1395  _numberOfRows = [_dataSource numberOfRowsInTableView:self] || 0;
1396  else
1397  {
1398  if (_dataSource)
1399  CPLog(@"no content binding established and data source " + [_dataSource description] + " does not implement numberOfRowsInTableView:");
1400  _numberOfRows = 0;
1401  }
1402 
1403  return _numberOfRows;
1404 }
1405 
1415 - (void)editColumn:(CPInteger)columnIndex row:(CPInteger)rowIndex withEvent:(CPEvent)theEvent select:(BOOL)flag
1416 {
1417  // FIX ME: Cocoa documentation says all this should be called in THIS method:
1418  // sets up the field editor, and sends selectWithFrame:inView:editor:delegate:start:length: and editWithFrame:inView:editor:delegate:event: to the field editor's NSCell object with the NSTableView as the text delegate.
1419 
1420  if (![self isRowSelected:rowIndex])
1421  [[CPException exceptionWithName:@"Error" reason:@"Attempt to edit row="+rowIndex+" when not selected." userInfo:nil] raise];
1422 
1423  [self scrollRowToVisible:rowIndex];
1424  [self scrollColumnToVisible:columnIndex];
1425 
1426  // TODO Do something with flag.
1427 
1428  _editingCellIndex = CGPointMake(columnIndex, rowIndex);
1429  _editingCellIndex._shouldSelect = flag;
1430 
1433 }
1434 
1438 - (CPInteger)editedColumn
1439 {
1440  if (!_editingCellIndex)
1441  return CPNotFound;
1442  return _editingCellIndex.x;
1443 }
1444 
1448 - (CPInteger)editedRow
1449 {
1450  if (!_editingCellIndex)
1451  return CPNotFound;
1452  return _editingCellIndex.y;
1453 }
1454 
1458 - (CPView)cornerView
1459 {
1460  return _cornerView;
1461 }
1462 
1466 - (void)setCornerView:(CPView)aView
1467 {
1468  if (_cornerView === aView)
1469  return;
1470 
1471  _cornerView = aView;
1472 
1473  var scrollView = [self enclosingScrollView];
1474 
1475  if ([scrollView isKindOfClass:[CPScrollView class]] && [scrollView documentView] === self)
1476  [scrollView _updateCornerAndHeaderView];
1477 }
1478 
1482 - (CPView)headerView
1483 {
1484  return _headerView;
1485 }
1486 
1487 
1495 - (void)setHeaderView:(CPView)aHeaderView
1496 {
1497  if (_headerView === aHeaderView)
1498  return;
1499 
1500  [_headerView setTableView:nil];
1501 
1502  _headerView = aHeaderView;
1503 
1504  if (_headerView)
1505  {
1506  [_headerView setTableView:self];
1507  [_headerView setFrameSize:_CGSizeMake(_CGRectGetWidth([self frame]), _CGRectGetHeight([_headerView frame]))];
1508  }
1509  else
1510  {
1511  // If there is no header view, there should be no corner view
1512  [_cornerView removeFromSuperview];
1513  _cornerView = nil;
1514  }
1515 
1516  var scrollView = [self enclosingScrollView];
1517 
1518  if ([scrollView isKindOfClass:[CPScrollView class]] && [scrollView documentView] === self)
1519  [scrollView _updateCornerAndHeaderView];
1520 
1521  [self setNeedsLayout];
1522 }
1523 
1524 // Complexity:
1525 // O(Columns)
1529 - (void)_recalculateTableColumnRanges
1530 {
1531  if (_dirtyTableColumnRangeIndex < 0)
1532  return;
1533 
1534  _numberOfHiddenColumns = 0;
1535 
1536  var index = _dirtyTableColumnRangeIndex,
1537  count = NUMBER_OF_COLUMNS(),
1538  x = index === 0 ? 0.0 : CPMaxRange(_tableColumnRanges[index - 1]);
1539 
1540  for (; index < count; ++index)
1541  {
1542  var tableColumn = _tableColumns[index];
1543 
1544  if ([tableColumn isHidden])
1545  {
1546  _numberOfHiddenColumns += 1;
1547  _tableColumnRanges[index] = CPMakeRange(x, 0.0);
1548  }
1549  else
1550  {
1551  var width = [_tableColumns[index] width] + _intercellSpacing.width;
1552 
1553  _tableColumnRanges[index] = CPMakeRange(x, width);
1554 
1555  x += width;
1556  }
1557  }
1558 
1559  _tableColumnRanges.length = count;
1560  _dirtyTableColumnRangeIndex = CPNotFound;
1561 }
1562 
1563 // Complexity:
1564 // O(1)
1571 - (CGRect)rectOfColumn:(CPInteger)aColumnIndex
1572 {
1573  // Convert e.g. "0" to 0.
1574  aColumnIndex = +aColumnIndex;
1575 
1576  if (aColumnIndex < 0 || aColumnIndex >= NUMBER_OF_COLUMNS())
1577  return _CGRectMakeZero();
1578 
1579  var column = [[self tableColumns] objectAtIndex:aColumnIndex];
1580 
1581  if ([column isHidden])
1582  return _CGRectMakeZero();
1583 
1585 
1586  var range = _tableColumnRanges[aColumnIndex];
1587 
1588  return _CGRectMake(range.location, 0.0, range.length, _CGRectGetHeight([self bounds]));
1589 }
1590 
1591 // Complexity:
1592 // O(1)
1600 - (CGRect)_rectOfRow:(CPInteger)aRowIndex checkRange:(BOOL)checkRange
1601 {
1602  var lastIndex = [self numberOfRows] - 1;
1603 
1604  if (checkRange && (aRowIndex > lastIndex || aRowIndex < 0))
1605  return _CGRectMakeZero();
1606 
1607  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_heightOfRow_)
1608  {
1609  var rowToLookUp = MIN(aRowIndex, lastIndex);
1610 
1611  // if the row doesn't exist
1612  if (rowToLookUp !== CPNotFound)
1613  {
1614  var y = _cachedRowHeights[rowToLookUp].heightAboveRow,
1615  height = _cachedRowHeights[rowToLookUp].height + _intercellSpacing.height,
1616  rowDelta = aRowIndex - rowToLookUp;
1617  }
1618  else
1619  {
1620  y = aRowIndex * (_rowHeight + _intercellSpacing.height);
1621  height = _rowHeight + _intercellSpacing.height;
1622  }
1623 
1624  // if we need the rect of a row past the last index
1625  if (rowDelta > 0)
1626  {
1627  y += rowDelta * (_rowHeight + _intercellSpacing.height);
1628  height = _rowHeight + _intercellSpacing.height;
1629  }
1630  }
1631  else
1632  {
1633  var y = aRowIndex * (_rowHeight + _intercellSpacing.height),
1634  height = _rowHeight + _intercellSpacing.height;
1635  }
1636 
1637  return _CGRectMake(0.0, y, _CGRectGetWidth([self bounds]), height);
1638 }
1639 
1645 - (CGRect)rectOfRow:(CPInteger)aRowIndex
1646 {
1647  return [self _rectOfRow:aRowIndex checkRange:YES];
1648 }
1649 
1650 // Complexity:
1651 // O(1)
1657 - (CPRange)rowsInRect:(CGRect)aRect
1658 {
1659  // If we have no rows, then we won't intersect anything.
1660  if (_numberOfRows <= 0)
1661  return CPMakeRange(0, 0);
1662 
1663  var bounds = [self bounds];
1664 
1665  // No rows if the rect doesn't even intersect us.
1666  if (!CGRectIntersectsRect(aRect, bounds))
1667  return CPMakeRange(0, 0);
1668 
1669  var firstRow = [self rowAtPoint:aRect.origin];
1670 
1671  // first row has to be undershot, because if not we wouldn't be intersecting.
1672  if (firstRow < 0)
1673  firstRow = 0;
1674 
1675  var lastRow = [self rowAtPoint:_CGPointMake(0.0, _CGRectGetMaxY(aRect))];
1676 
1677  // last row has to be overshot, because if not we wouldn't be intersecting.
1678  if (lastRow < 0)
1679  lastRow = _numberOfRows - 1;
1680 
1681  return CPMakeRange(firstRow, lastRow - firstRow + 1);
1682 }
1683 
1688 - (CPRange)_unboundedRowsInRect:(CGRect)aRect
1689 {
1690  var boundedRange = [self rowsInRect:aRect],
1691  lastRow = CPMaxRange(boundedRange),
1692  rectOfLastRow = [self _rectOfRow:lastRow checkRange:NO],
1693  bottom = _CGRectGetMaxY(aRect),
1694  bottomOfBoundedRows = _CGRectGetMaxY(rectOfLastRow);
1695 
1696  // we only have to worry about the rows below the last...
1697  if (bottom <= bottomOfBoundedRows)
1698  return boundedRange;
1699 
1700  var numberOfNewRows = CEIL(bottom - bottomOfBoundedRows) / ([self rowHeight] + _intercellSpacing.height);
1701 
1702  boundedRange.length += numberOfNewRows + 1;
1703 
1704  return boundedRange;
1705 }
1706 
1707 // Complexity:
1708 // O(lg Columns) if table view contains no hidden columns
1709 // O(Columns) if table view contains hidden columns
1710 
1716 - (CPIndexSet)columnIndexesInRect:(CGRect)aRect
1717 {
1718  var column = MAX(0, [self columnAtPoint:_CGPointMake(aRect.origin.x, 0.0)]),
1719  lastColumn = [self columnAtPoint:_CGPointMake(_CGRectGetMaxX(aRect), 0.0)];
1720 
1721  if (lastColumn === CPNotFound)
1722  lastColumn = NUMBER_OF_COLUMNS() - 1;
1723 
1724  // Don't bother doing the expensive removal of hidden indexes if we have no hidden columns.
1725  if (_numberOfHiddenColumns <= 0)
1726  return [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(column, lastColumn - column + 1)];
1727 
1728  //
1729  var indexSet = [CPIndexSet indexSet];
1730 
1731  for (; column <= lastColumn; ++column)
1732  {
1733  var tableColumn = _tableColumns[column];
1734 
1735  if (![tableColumn isHidden])
1736  [indexSet addIndex:column];
1737  }
1738 
1739  return indexSet;
1740 }
1741 
1742 // Complexity:
1743 // O(lg Columns) if table view contains now hidden columns
1744 // O(Columns) if table view contains hidden columns
1750 - (CPInteger)columnAtPoint:(CGPoint)aPoint
1751 {
1752  var bounds = [self bounds];
1753 
1754  if (!_CGRectContainsPoint(bounds, aPoint))
1755  return CPNotFound;
1756 
1758 
1759  var x = aPoint.x,
1760  low = 0,
1761  high = _tableColumnRanges.length - 1;
1762 
1763  while (low <= high)
1764  {
1765  var middle = FLOOR(low + (high - low) / 2),
1766  range = _tableColumnRanges[middle];
1767 
1768  if (x < range.location)
1769  high = middle - 1;
1770 
1771  else if (x >= CPMaxRange(range))
1772  low = middle + 1;
1773 
1774  else
1775  {
1776  var numberOfColumns = _tableColumnRanges.length;
1777 
1778  while (middle < numberOfColumns && [_tableColumns[middle] isHidden])
1779  ++middle;
1780 
1781  if (middle < numberOfColumns)
1782  return middle;
1783 
1784  return CPNotFound;
1785  }
1786  }
1787 
1788  return CPNotFound;
1789 }
1790 
1791 //Complexity
1792 // O(1) for static row height
1793 // 0(lg Rows) for variable row heights
1799 - (CPInteger)rowAtPoint:(CGPoint)aPoint
1800 {
1801  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_heightOfRow_)
1802  {
1803  return [_cachedRowHeights indexOfObject:aPoint
1804  inSortedRange:nil
1805  options:0
1806  usingComparator:function(aPoint, rowCache)
1807  {
1808  var upperBound = rowCache.heightAboveRow;
1809 
1810  if (aPoint.y < upperBound)
1811  return CPOrderedAscending;
1812 
1813  if (aPoint.y > upperBound + rowCache.height + _intercellSpacing.height)
1814  return CPOrderedDescending;
1815 
1816  return CPOrderedSame;
1817  }];
1818  }
1819 
1820  var y = aPoint.y,
1821  row = FLOOR(y / (_rowHeight + _intercellSpacing.height));
1822 
1823  if (row >= _numberOfRows)
1824  return CPNotFound;
1825 
1826  return row;
1827 }
1828 
1836 - (CGRect)frameOfDataViewAtColumn:(CPInteger)aColumn row:(CPInteger)aRow
1837 {
1839 
1840  if (aColumn > [self numberOfColumns] || aRow > [self numberOfRows])
1841  return _CGRectMakeZero();
1842 
1843  var tableColumnRange = _tableColumnRanges[aColumn],
1844  rectOfRow = [self rectOfRow:aRow],
1845  leftInset = FLOOR(_intercellSpacing.width / 2.0),
1846  topInset = FLOOR(_intercellSpacing.height / 2.0);
1847 
1848  return _CGRectMake(tableColumnRange.location + leftInset, _CGRectGetMinY(rectOfRow) + topInset, tableColumnRange.length - _intercellSpacing.width, _CGRectGetHeight(rectOfRow) - _intercellSpacing.height);
1849 }
1850 
1854 - (void)resizeWithOldSuperviewSize:(CGSize)aSize
1855 {
1856  [super resizeWithOldSuperviewSize:aSize];
1857 
1858  if (_disableAutomaticResizing)
1859  return;
1860 
1861  var mask = _columnAutoResizingStyle;
1862 
1863  // should we actually do some resizing?
1864  if (!_lastColumnShouldSnap)
1865  {
1866  // did the clip view intersect the old tablesize?
1867  var superview = [self superview];
1868 
1869  if (!superview || ![superview isKindOfClass:[CPClipView class]])
1870  return;
1871 
1872  var superviewWidth = [superview bounds].size.width,
1873  lastColumnMaxX = _CGRectGetMaxX([self rectOfColumn:[self numberOfColumns] -1]);
1874 
1875  // Fix me: this fires on the table setup at times
1876  if (lastColumnMaxX >= superviewWidth && lastColumnMaxX <= aSize.width || lastColumnMaxX <= superviewWidth && lastColumnMaxX >= aSize.width)
1877  _lastColumnShouldSnap = YES;
1879  return;
1880  }
1881 
1883  [self _resizeAllColumnUniformlyWithOldSize:aSize];
1885  [self sizeLastColumnToFit];
1887  [self _autoResizeFirstColumn];
1888 }
1889 
1893 - (void)_autoResizeFirstColumn
1894 {
1895  var superview = [self superview];
1896 
1897  if (!superview)
1898  return;
1899 
1901 
1902  var count = NUMBER_OF_COLUMNS(),
1903  columnToResize = nil,
1904  totalWidth = 0,
1905  i = 0;
1906 
1907  for (; i < count; i++)
1908  {
1909  var column = _tableColumns[i];
1910 
1911  if (![column isHidden])
1912  {
1913  if (!columnToResize)
1914  columnToResize = column;
1915  totalWidth += [column width] + _intercellSpacing.width;
1916  }
1917  }
1918 
1919  // If there is a visible column
1920  if (columnToResize)
1921  {
1922  var superviewSize = [superview bounds].size,
1923  newWidth = superviewSize.width - totalWidth;
1924 
1925  newWidth += [columnToResize width];
1926  [columnToResize _tryToResizeToWidth:newWidth];
1927  }
1928 
1929  [self setNeedsLayout];
1930 }
1931 
1932 
1937 - (void)_resizeAllColumnUniformlyWithOldSize:(CGSize)oldSize
1938 {
1939  // what we care about is the superview clip rect
1940  // FIX ME: if it's not in a scrollview this doesn't really work
1941  var superview = [self superview];
1942 
1943  if (!superview || ![superview isKindOfClass:[CPClipView class]])
1944  return;
1945 
1947 
1948  var superviewWidth = [superview bounds].size.width,
1949  count = NUMBER_OF_COLUMNS(),
1950  resizableColumns = [CPIndexSet indexSet],
1951  remainingSpace = 0.0,
1952  i = 0;
1953 
1954  // find resizable columns
1955  // FIX ME: we could cache resizableColumns after this loop and reuse it during the resize
1956  for (; i < count; i++)
1957  {
1958  var tableColumn = _tableColumns[i];
1959  if (![tableColumn isHidden] && ([tableColumn resizingMask] & CPTableColumnAutoresizingMask))
1960  [resizableColumns addIndex:i];
1961  }
1962 
1963  var maxXofColumns = _CGRectGetMaxX([self rectOfColumn:[resizableColumns lastIndex]]),
1964  remainingSpace = superviewWidth - maxXofColumns,
1965  resizeableColumnsCount = [resizableColumns count],
1966  proportionate = 0;
1967 
1968  while (remainingSpace && resizeableColumnsCount)
1969  {
1970  // Divy out the space.
1971  proportionate += remainingSpace / resizeableColumnsCount;
1972 
1973  // Reset the remaining space to 0
1974  remainingSpace = 0.0;
1975 
1976  var index = CPNotFound;
1977 
1978  while ((index = [resizableColumns indexGreaterThanIndex:index]) !== CPNotFound)
1979  {
1980  var item = _tableColumns[index],
1981  proposedWidth = [item width] + proportionate,
1982  resizeLeftovers = [item _tryToResizeToWidth:proposedWidth];
1983 
1984  if (resizeLeftovers)
1985  {
1986  [resizableColumns removeIndex:index];
1987 
1988  remainingSpace += resizeLeftovers;
1989  }
1990  }
1991  }
1992 
1993  // now that we've reached the end we know there are likely rounding errors
1994  // so we should size the last resized to fit
1995 
1996  // find the last visisble column
1997  while (count-- && [_tableColumns[count] isHidden]);
1998 
1999  // find the max x, but subtract a single pixel since the spacing isn't applicable here.
2000  var delta = superviewWidth - _CGRectGetMaxX([self rectOfColumn:count]) - ([self intercellSpacing].width || 1),
2001  newSize = [item width] + delta;
2002 
2003  [item _tryToResizeToWidth:newSize];
2004 }
2005 
2017 - (void)setColumnAutoresizingStyle:(unsigned)style
2018 {
2019  //FIX ME: CPTableViewSequentialColumnAutoresizingStyle and CPTableViewReverseSequentialColumnAutoresizingStyle are not yet implemented
2020  _columnAutoResizingStyle = style;
2021 }
2022 
2026 - (unsigned)columnAutoresizingStyle
2027 {
2028  return _columnAutoResizingStyle;
2029 }
2030 
2034 - (void)sizeLastColumnToFit
2035 {
2036  _lastColumnShouldSnap = YES;
2037 
2038  var superview = [self superview];
2039 
2040  if (!superview)
2041  return;
2042 
2043  var superviewSize = [superview bounds].size;
2044 
2046 
2047  var count = NUMBER_OF_COLUMNS();
2048 
2049  // Decrement the counter until we get to the last column that's not hidden
2050  while (count-- && [_tableColumns[count] isHidden]);
2051 
2052  // If the last column exists
2053  if (count >= 0)
2054  {
2055  var columnToResize = _tableColumns[count],
2056  newSize = MAX(0.0, superviewSize.width - CGRectGetMinX([self rectOfColumn:count]) - _intercellSpacing.width);
2057 
2058  [columnToResize _tryToResizeToWidth:newSize];
2059  }
2060 
2061  [self setNeedsLayout];
2062 }
2063 
2067 - (void)noteNumberOfRowsChanged
2068 {
2069  var oldNumberOfRows = _numberOfRows;
2070 
2071  _numberOfRows = nil;
2072  _cachedRowHeights = [];
2073 
2074  // this line serves two purposes
2075  // 1. it updates the _numberOfRows cache with the -numberOfRows call
2076  // 2. it updates the row height cache if needed
2078 
2079  // remove row indexes from the selection if they no longer exist
2080  var hangingSelections = oldNumberOfRows - _numberOfRows;
2081 
2082  if (hangingSelections > 0)
2083  {
2084 
2085  var previousSelectionCount = [_selectedRowIndexes count];
2086  [_selectedRowIndexes removeIndexesInRange:CPMakeRange(_numberOfRows, hangingSelections)];
2087 
2088  if (![_selectedRowIndexes containsIndex:[self selectedRow]])
2089  _lastSelectedRow = CPNotFound;
2090 
2091  // For optimal performance, only send a notification if indices were actually removed.
2092  if (previousSelectionCount > [_selectedRowIndexes count])
2093  [self _noteSelectionDidChange];
2094  }
2095 
2096  [self tile];
2097 }
2098 
2099 
2105 - (void)noteHeightOfRowsWithIndexesChanged:(CPIndexSet)anIndexSet
2106 {
2107  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_heightOfRow_))
2108  return;
2109 
2110  // this method will update the height of those rows, but since the cached array also contains
2111  // the height above the row it needs to recalculate for the rows below it too
2112  var i = [anIndexSet firstIndex],
2113  count = _numberOfRows - i,
2114  heightAbove = (i > 0) ? _cachedRowHeights[i - 1].height + _cachedRowHeights[i - 1].heightAboveRow + _intercellSpacing.height : 0;
2115 
2116  for (; i < count; i++)
2117  {
2118  // update the cache if the user told us to
2119  if ([anIndexSet containsIndex:i])
2120  var height = [_delegate tableView:self heightOfRow:i];
2121 
2122  _cachedRowHeights[i] = {"height":height, "heightAboveRow":heightAbove};
2123 
2124  heightAbove += height + _intercellSpacing.height;
2125  }
2126 }
2127 
2131 - (void)tile
2132 {
2134 
2135  var width = _tableColumnRanges.length > 0 ? CPMaxRange([_tableColumnRanges lastObject]) : 0.0,
2136  superview = [self superview];
2137 
2138  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_heightOfRow_))
2139  var height = (_rowHeight + _intercellSpacing.height) * _numberOfRows;
2140  else if ([self numberOfRows] === 0)
2141  var height = 0;
2142  else
2143  {
2144  // if this is the fist run we need to populate the cache
2145  if ([self numberOfRows] !== _cachedRowHeights.length)
2147 
2148  var heightObject = _cachedRowHeights[_cachedRowHeights.length - 1],
2149  height = heightObject.heightAboveRow + heightObject.height + _intercellSpacing.height;
2150 
2151  }
2152 
2153 
2154  if ([superview isKindOfClass:[CPClipView class]])
2155  {
2156  var superviewSize = [superview bounds].size;
2157 
2158  width = MAX(superviewSize.width, width);
2159  height = MAX(superviewSize.height, height);
2160  }
2161 
2162  [self setFrameSize:_CGSizeMake(width, height)];
2163 
2164  [self setNeedsLayout];
2165  [self setNeedsDisplay:YES];
2166 }
2167 
2168 
2174 - (void)scrollRowToVisible:(int)rowIndex
2175 {
2176  var visible = [self visibleRect],
2177  rowRect = [self rectOfRow:rowIndex];
2178 
2179  visible.origin.y = rowRect.origin.y;
2180  visible.size.height = rowRect.size.height;
2181 
2182  [self scrollRectToVisible:visible];
2183 }
2184 
2190 - (void)scrollColumnToVisible:(int)columnIndex
2191 {
2192  var visible = [self visibleRect],
2193  colRect = [self rectOfColumn:columnIndex];
2194 
2195  visible.origin.x = colRect.origin.x;
2196  visible.size.width = colRect.size.width;
2197 
2198  [self scrollRectToVisible:visible];
2199  [_headerView scrollRectToVisible:colRect];
2200 }
2201 
2208 - (void)setAutosaveName:(CPString)theAutosaveName
2209 {
2210  if (_autosaveName === theAutosaveName)
2211  return;
2212 
2213  _autosaveName = theAutosaveName;
2214 
2215  [self setAutosaveTableColumns:!!theAutosaveName];
2216  [self _restoreFromAutosave];
2217 }
2218 
2222 - (CPString)autosaveName
2223 {
2224  return _autosaveName;
2225 }
2226 
2233 - (void)setAutosaveTableColumns:(BOOL)shouldAutosave
2234 {
2235  _autosaveTableColumns = shouldAutosave;
2236 }
2237 
2241 - (BOOL)autosaveTableColumns
2242 {
2243  return _autosaveTableColumns;
2244 }
2245 
2249 - (CPString)_columnsKeyForAutosaveName:(CPString)theAutosaveName
2250 {
2251  return @"CPTableView Columns " + theAutosaveName;
2252 }
2253 
2257 - (BOOL)_autosaveEnabled
2258 {
2259  return [self autosaveName] && [self autosaveTableColumns];
2260 }
2261 
2268 - (void)_autosave
2269 {
2270  if (![self _autosaveEnabled])
2271  return;
2272 
2273  var userDefaults = [CPUserDefaults standardUserDefaults],
2274  autosaveName = [self autosaveName];
2275 
2276  var columns = [self tableColumns],
2277  columnsSetup = [];
2278 
2279  for (var i = 0; i < [columns count]; i++)
2280  {
2281  var column = [columns objectAtIndex:i],
2282  metaData = [CPDictionary dictionaryWithJSObject:{
2283  @"identifier": [column identifier],
2284  @"width": [column width]
2285  }];
2286 
2287  [columnsSetup addObject:metaData];
2288  }
2289 
2290  [userDefaults setObject:columnsSetup forKey:[self _columnsKeyForAutosaveName:autosaveName]];
2291 }
2292 
2296 - (void)_restoreFromAutosave
2297 {
2298  if (![self _autosaveEnabled])
2299  return;
2300 
2301  var userDefaults = [CPUserDefaults standardUserDefaults],
2302  autosaveName = [self autosaveName],
2303  tableColumns = [userDefaults objectForKey:[self _columnsKeyForAutosaveName:autosaveName]];
2304 
2305  for (var i = 0; i < [tableColumns count]; i++)
2306  {
2307  var metaData = [tableColumns objectAtIndex:i],
2308  columnIdentifier = [metaData objectForKey:@"identifier"],
2309  column = [self columnWithIdentifier:columnIdentifier],
2310  tableColumn = [self tableColumnWithIdentifier:columnIdentifier];
2311 
2312  [self _moveColumn:column toColumn:i];
2313  [tableColumn setWidth:[metaData objectForKey:@"width"]];
2314  }
2315 }
2316 
2317 
2455 - (void)setDelegate:(id)aDelegate
2456 {
2457  if (_delegate === aDelegate)
2458  return;
2459 
2460  var defaultCenter = [CPNotificationCenter defaultCenter];
2461 
2462  if (_delegate)
2463  {
2464  if ([_delegate respondsToSelector:@selector(tableViewColumnDidMove:)])
2465  [defaultCenter
2466  removeObserver:_delegate
2468  object:self];
2469 
2470  if ([_delegate respondsToSelector:@selector(tableViewColumnDidResize:)])
2471  [defaultCenter
2472  removeObserver:_delegate
2474  object:self];
2475 
2476  if ([_delegate respondsToSelector:@selector(tableViewSelectionDidChange:)])
2477  [defaultCenter
2478  removeObserver:_delegate
2480  object:self];
2481 
2482  if ([_delegate respondsToSelector:@selector(tableViewSelectionIsChanging:)])
2483  [defaultCenter
2484  removeObserver:_delegate
2486  object:self];
2487  }
2488 
2489  _delegate = aDelegate;
2490  _implementedDelegateMethods = 0;
2491 
2492  if ([_delegate respondsToSelector:@selector(selectionShouldChangeInTableView:)])
2493  _implementedDelegateMethods |= CPTableViewDelegate_selectionShouldChangeInTableView_;
2494 
2495  if ([_delegate respondsToSelector:@selector(tableView:dataViewForTableColumn:row:)])
2496  _implementedDelegateMethods |= CPTableViewDelegate_tableView_dataViewForTableColumn_row_;
2497 
2498  if ([_delegate respondsToSelector:@selector(tableView:didClickTableColumn:)])
2499  _implementedDelegateMethods |= CPTableViewDelegate_tableView_didClickTableColumn_;
2500 
2501  if ([_delegate respondsToSelector:@selector(tableView:didDragTableColumn:)])
2502  _implementedDelegateMethods |= CPTableViewDelegate_tableView_didDragTableColumn_;
2503 
2504  if ([_delegate respondsToSelector:@selector(tableView:heightOfRow:)])
2505  _implementedDelegateMethods |= CPTableViewDelegate_tableView_heightOfRow_;
2506 
2507  if ([_delegate respondsToSelector:@selector(tableView:isGroupRow:)])
2508  _implementedDelegateMethods |= CPTableViewDelegate_tableView_isGroupRow_;
2509 
2510  if ([_delegate respondsToSelector:@selector(tableView:mouseDownInHeaderOfTableColumn:)])
2512 
2513  if ([_delegate respondsToSelector:@selector(tableView:nextTypeSelectMatchFromRow:toRow:forString:)])
2515 
2516  if ([_delegate respondsToSelector:@selector(tableView:selectionIndexesForProposedSelection:)])
2518 
2519  if ([_delegate respondsToSelector:@selector(tableView:shouldEditTableColumn:row:)])
2520  _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldEditTableColumn_row_;
2521 
2522  if ([_delegate respondsToSelector:@selector(tableView:shouldSelectRow:)])
2523  _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldSelectRow_;
2524 
2525  if ([_delegate respondsToSelector:@selector(tableView:shouldSelectTableColumn:)])
2526  _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldSelectTableColumn_;
2527 
2528  if ([_delegate respondsToSelector:@selector(tableView:shouldShowViewExpansionForTableColumn:row:)])
2530 
2531  if ([_delegate respondsToSelector:@selector(tableView:shouldTrackView:forTableColumn:row:)])
2533 
2534  if ([_delegate respondsToSelector:@selector(tableView:shouldTypeSelectForEvent:withCurrentSearchString:)])
2536 
2537  if ([_delegate respondsToSelector:@selector(tableView:toolTipForView:rect:tableColumn:row:mouseLocation:)])
2539 
2540  if ([_delegate respondsToSelector:@selector(tableView:typeSelectStringForTableColumn:row:)])
2542 
2543  if ([_delegate respondsToSelector:@selector(tableView:willDisplayView:forTableColumn:row:)])
2545 
2546  if ([_delegate respondsToSelector:@selector(tableView:menuForTableColumn:row:)])
2547  _implementedDelegateMethods |= CPTableViewDelegate_tableViewMenuForTableColumn_Row_;
2548 
2549  if ([_delegate respondsToSelector:@selector(tableViewColumnDidMove:)])
2550  [defaultCenter
2551  addObserver:_delegate
2552  selector:@selector(tableViewColumnDidMove:)
2554  object:self];
2555 
2556  if ([_delegate respondsToSelector:@selector(tableViewColumnDidResize:)])
2557  [defaultCenter
2558  addObserver:_delegate
2559  selector:@selector(tableViewColumnDidResize:)
2561  object:self];
2562 
2563  if ([_delegate respondsToSelector:@selector(tableViewSelectionDidChange:)])
2564  [defaultCenter
2565  addObserver:_delegate
2566  selector:@selector(tableViewSelectionDidChange:)
2568  object:self];
2569 
2570  if ([_delegate respondsToSelector:@selector(tableViewSelectionIsChanging:)])
2571  [defaultCenter
2572  addObserver:_delegate
2573  selector:@selector(tableViewSelectionIsChanging:)
2575  object:self];
2576 }
2577 
2581 - (id)delegate
2582 {
2583  return _delegate;
2584 }
2585 
2589 - (void)_sendDelegateDidClickColumn:(int)column
2590 {
2591  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_didClickTableColumn_)
2592  [_delegate tableView:self didClickTableColumn:_tableColumns[column]];
2593 }
2594 
2598 - (void)_sendDelegateDidDragColumn:(int)column
2599 {
2600  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_didDragTableColumn_)
2601  [_delegate tableView:self didDragTableColumn:_tableColumns[column]];
2602 }
2603 
2604 - (void)_sendDelegateDidMouseDownInHeader:(int)column
2605 {
2606  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_mouseDownInHeaderOfTableColumn_)
2607  [_delegate tableView:self mouseDownInHeaderOfTableColumn:_tableColumns[column]];
2608 }
2609 
2610 /*
2611  @ignore
2612 */
2613 - (BOOL)_sendDelegateDeleteKeyPressed
2614 {
2615  if ([_delegate respondsToSelector: @selector(tableViewDeleteKeyPressed:)])
2616  {
2617  [_delegate tableViewDeleteKeyPressed:self];
2618  return YES;
2619  }
2620 
2621  return NO;
2622 }
2623 
2624 
2628 - (void)_sendDataSourceSortDescriptorsDidChange:(CPArray)oldDescriptors
2629 {
2630  if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_sortDescriptorsDidChange_)
2631  [_dataSource tableView:self sortDescriptorsDidChange:oldDescriptors];
2632 }
2633 
2634 
2638 - (void)_didClickTableColumn:(int)clickedColumn modifierFlags:(unsigned)modifierFlags
2639 {
2640  [self _sendDelegateDidClickColumn:clickedColumn];
2641 
2642  if (_allowsColumnSelection)
2643  {
2644  [self _noteSelectionIsChanging];
2645  if (modifierFlags & CPPlatformActionKeyMask)
2646  {
2647  if ([self isColumnSelected:clickedColumn])
2648  [self deselectColumn:clickedColumn];
2649  else if ([self allowsMultipleSelection] == YES)
2650  [self selectColumnIndexes:[CPIndexSet indexSetWithIndex:clickedColumn] byExtendingSelection:YES];
2651 
2652  return;
2653  }
2654  else if (modifierFlags & CPShiftKeyMask)
2655  {
2656  // should be from clickedColumn to lastClickedColum with extending:(direction == previous selection)
2657  var startColumn = MIN(clickedColumn, [_selectedColumnIndexes lastIndex]),
2658  endColumn = MAX(clickedColumn, [_selectedColumnIndexes firstIndex]);
2659 
2660  [self selectColumnIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(startColumn, endColumn - startColumn + 1)]
2661  byExtendingSelection:YES];
2662 
2663  return;
2664  }
2665  else
2666  [self selectColumnIndexes:[CPIndexSet indexSetWithIndex:clickedColumn] byExtendingSelection:NO];
2667  }
2668 
2669  [self _changeSortDescriptorsForClickOnColumn:clickedColumn];
2670 }
2671 
2672 // From GNUSTEP
2676 - (void)_changeSortDescriptorsForClickOnColumn:(int)column
2677 {
2678  var tableColumn = [_tableColumns objectAtIndex:column],
2679  newMainSortDescriptor = [tableColumn sortDescriptorPrototype];
2680 
2681  if (!newMainSortDescriptor)
2682  return;
2683 
2684  var oldMainSortDescriptor = nil,
2685  oldSortDescriptors = [self sortDescriptors],
2686  newSortDescriptors = [CPArray arrayWithArray:oldSortDescriptors],
2687 
2688  e = [newSortDescriptors objectEnumerator],
2689  descriptor = nil,
2690  outdatedDescriptors = [CPArray array];
2691 
2692  if ([_sortDescriptors count] > 0)
2693  oldMainSortDescriptor = [[self sortDescriptors] objectAtIndex: 0];
2694 
2695  // Remove every main descriptor equivalents (normally only one)
2696  while ((descriptor = [e nextObject]) !== nil)
2697  {
2698  if ([[descriptor key] isEqual: [newMainSortDescriptor key]])
2699  [outdatedDescriptors addObject:descriptor];
2700  }
2701 
2702  // Invert the sort direction when the same column header is clicked twice
2703  if ([[newMainSortDescriptor key] isEqual:[oldMainSortDescriptor key]])
2704  newMainSortDescriptor = [oldMainSortDescriptor reversedSortDescriptor];
2705 
2706  [newSortDescriptors removeObjectsInArray:outdatedDescriptors];
2707  [newSortDescriptors insertObject:newMainSortDescriptor atIndex:0];
2708 
2709  [self setHighlightedTableColumn:tableColumn];
2710  [self setSortDescriptors:newSortDescriptors];
2711 }
2712 
2721 - (void)setIndicatorImage:(CPImage)anImage inTableColumn:(CPTableColumn)aTableColumn
2722 {
2723  if (aTableColumn)
2724  {
2725  var headerView = [aTableColumn headerView];
2726  if ([headerView respondsToSelector:@selector(_setIndicatorImage:)])
2727  [headerView _setIndicatorImage:anImage];
2728  }
2729 }
2730 
2734 - (CPImage)_tableHeaderSortImage
2735 {
2736  return [self currentValueForThemeAttribute:@"sort-image"];
2737 }
2738 
2742 - (CPImage)_tableHeaderReverseSortImage
2743 {
2744  return [self currentValueForThemeAttribute:@"sort-image-reversed"];
2745 }
2746 
2750 - (CPTableColumn)highlightedTableColumn
2751 {
2752  return _currentHighlightedTableColumn;
2753 }
2754 
2758 - (void)setHighlightedTableColumn:(CPTableColumn)aTableColumn
2759 {
2760  if (_currentHighlightedTableColumn == aTableColumn)
2761  return;
2762 
2763  if (_headerView)
2764  {
2765  if (_currentHighlightedTableColumn != nil)
2766  [[_currentHighlightedTableColumn headerView] unsetThemeState:CPThemeStateSelected];
2767 
2768  if (aTableColumn != nil)
2769  [[aTableColumn headerView] setThemeState:CPThemeStateSelected];
2770  }
2771 
2772  _currentHighlightedTableColumn = aTableColumn;
2773 }
2774 
2781 - (BOOL)canDragRowsWithIndexes:(CPIndexSet)rowIndexes atPoint:(CGPoint)mouseDownPoint
2782 {
2783  return [rowIndexes count] > 0 && [self numberOfRows] > 0;
2784 }
2785 
2796 - (CPImage)dragImageForRowsWithIndexes:(CPIndexSet)dragRows tableColumns:(CPArray)theTableColumns event:(CPEvent)dragEvent offset:(CGPoint)dragImageOffset
2797 {
2798  return [[CPImage alloc] initWithContentsOfFile:@"Frameworks/AppKit/Resources/GenericFile.png" size:CGSizeMake(32,32)];
2799 }
2800 
2813 - (CPView)dragViewForRowsWithIndexes:(CPIndexSet)theDraggedRows tableColumns:(CPArray)theTableColumns event:(CPEvent)theDragEvent offset:(CGPoint)dragViewOffset
2814 {
2815  var bounds = [self bounds],
2816  view = [[CPView alloc] initWithFrame:bounds];
2817 
2818  [view setAlphaValue:0.7];
2819 
2820  // We have to fetch all the data views for the selected rows and columns
2821  // After that we can copy these add them to a transparent drag view and use that drag view
2822  // to make it appear we are dragging images of those rows (as you would do in regular Cocoa)
2823  var columnIndex = [theTableColumns count];
2824  while (columnIndex--)
2825  {
2826  var tableColumn = [theTableColumns objectAtIndex:columnIndex],
2827  row = [theDraggedRows firstIndex];
2828 
2829  while (row !== CPNotFound)
2830  {
2831  var dataView = [self _newDataViewForRow:row tableColumn:tableColumn];
2832 
2833  [dataView setFrame:[self frameOfDataViewAtColumn:columnIndex row:row]];
2834 
2835  [self _setObjectValueForTableColumn:tableColumn row:row forView:dataView];
2836  [view addSubview:dataView];
2837 
2838  row = [theDraggedRows indexGreaterThanIndex:row];
2839  }
2840  }
2841 
2842  var dragPoint = [self convertPoint:[theDragEvent locationInWindow] fromView:nil];
2843  dragViewOffset.x = _CGRectGetWidth(bounds) / 2 - dragPoint.x;
2844  dragViewOffset.y = _CGRectGetHeight(bounds) / 2 - dragPoint.y;
2845 
2846  return view;
2847 }
2848 
2855 - (CPView)_dragViewForColumn:(int)theColumnIndex event:(CPEvent)theDragEvent offset:(CPPointPointer)theDragViewOffset
2856 {
2857  var dragView = [[_CPColumnDragView alloc] initWithLineColor:[self gridColor]],
2858  tableColumn = [[self tableColumns] objectAtIndex:theColumnIndex],
2859  bounds = _CGRectMake(0.0, 0.0, [tableColumn width], _CGRectGetHeight([self exposedRect]) + 23.0),
2860  columnRect = [self rectOfColumn:theColumnIndex],
2861  headerView = [tableColumn headerView],
2862  row = [_exposedRows firstIndex];
2863 
2864  while (row !== CPNotFound)
2865  {
2866  var dataView = [self _newDataViewForRow:row tableColumn:tableColumn],
2867  dataViewFrame = [self frameOfDataViewAtColumn:theColumnIndex row:row];
2868 
2869  // Only one column is ever dragged so we just place the view at
2870  dataViewFrame.origin.x = 0.0;
2871 
2872  // Offset by table header height - scroll position
2873  dataViewFrame.origin.y = ( _CGRectGetMinY(dataViewFrame) - _CGRectGetMinY([self exposedRect]) ) + 23.0;
2874  [dataView setFrame:dataViewFrame];
2875 
2876  [self _setObjectValueForTableColumn:tableColumn row:row forView:dataView];
2877  [dragView addSubview:dataView];
2878 
2879  row = [_exposedRows indexGreaterThanIndex:row];
2880  }
2881 
2882  // Add a copy of the header view.
2884  [dragView addSubview:columnHeaderView];
2885 
2886  [dragView setBackgroundColor:[CPColor whiteColor]];
2887  [dragView setAlphaValue:0.7];
2888  [dragView setFrame:bounds];
2889 
2890  return dragView;
2891 }
2892 
2897 - (void)setDraggingSourceOperationMask:(CPDragOperation)mask forLocal:(BOOL)isLocal
2898 {
2899  //ignore local for the time being since only one capp app can run at a time...
2900  _dragOperationDefaultMask = mask;
2901 }
2902 
2908 - (void)setDropRow:(CPInteger)row dropOperation:(CPTableViewDropOperation)operation
2909 {
2910  if (row > [self numberOfRows] && operation === CPTableViewDropOn)
2911  {
2912  var numberOfRows = [self numberOfRows] + 1,
2913  reason = @"Attempt to set dropRow=" + row +
2914  " dropOperation=CPTableViewDropOn when [0 - " + numberOfRows + "] is valid range of rows.";
2915 
2916  [[CPException exceptionWithName:@"Error" reason:reason userInfo:nil] raise];
2917  }
2918 
2919 
2920  _retargetedDropRow = row;
2921  _retargetedDropOperation = operation;
2922 }
2923 
2935 - (void)setDraggingDestinationFeedbackStyle:(CPTableViewDraggingDestinationFeedbackStyle)aStyle
2936 {
2937  //FIX ME: this should vary up the highlight color, currently nothing is being done with it
2938  _destinationDragStyle = aStyle;
2939 }
2940 
2951 - (CPTableViewDraggingDestinationFeedbackStyle)draggingDestinationFeedbackStyle
2952 {
2953  return _destinationDragStyle;
2954 }
2955 
2961 - (void)setVerticalMotionCanBeginDrag:(BOOL)aFlag
2962 {
2963  _verticalMotionCanDrag = aFlag;
2964 }
2965 
2969 - (BOOL)verticalMotionCanBeginDrag
2970 {
2971  return _verticalMotionCanDrag;
2972 }
2973 
2974 - (CPTableColumn)_tableColumnForSortDescriptor:(CPSortDescriptor)theSortDescriptor
2975 {
2976  var tableColumns = [self tableColumns];
2977 
2978  for (var i = 0; i < [tableColumns count]; i++)
2979  {
2980  var tableColumn = [tableColumns objectAtIndex:i],
2981  sortDescriptorPrototype = [tableColumn sortDescriptorPrototype];
2982 
2983  if (!sortDescriptorPrototype)
2984  continue;
2985 
2986  if ([sortDescriptorPrototype key] === [theSortDescriptor key]
2987  && [sortDescriptorPrototype selector] === [theSortDescriptor selector])
2988  {
2989  return tableColumn;
2990  }
2991  }
2992 
2993  return nil;
2994 }
2995 
3001 - (void)setSortDescriptors:(CPArray)sortDescriptors
3002 {
3003  var oldSortDescriptors = [[self sortDescriptors] copy],
3004  newSortDescriptors = nil;
3005 
3006  if (sortDescriptors == nil)
3007  newSortDescriptors = [CPArray array];
3008  else
3009  newSortDescriptors = [CPArray arrayWithArray:sortDescriptors];
3010 
3011  if ([newSortDescriptors isEqual:oldSortDescriptors])
3012  return;
3013 
3014  _sortDescriptors = newSortDescriptors;
3015 
3016  var oldColumn = nil,
3017  newColumn = nil;
3018 
3019  if ([newSortDescriptors count] > 0)
3020  {
3021  var newMainSortDescriptor = [newSortDescriptors objectAtIndex:0];
3022  newColumn = [self _tableColumnForSortDescriptor:newMainSortDescriptor];
3023  }
3024 
3025  if ([oldSortDescriptors count] > 0)
3026  {
3027  var oldMainSortDescriptor = [oldSortDescriptors objectAtIndex:0];
3028  oldColumn = [self _tableColumnForSortDescriptor:oldMainSortDescriptor];
3029  }
3030 
3031  var image = [newMainSortDescriptor ascending] ? [self _tableHeaderSortImage] : [self _tableHeaderReverseSortImage];
3032  [self setIndicatorImage:nil inTableColumn:oldColumn];
3033  [self setIndicatorImage:image inTableColumn:newColumn];
3034 
3035  [self _sendDataSourceSortDescriptorsDidChange:oldSortDescriptors];
3036 
3037  var binderClass = [[self class] _binderClassForBinding:@"sortDescriptors"];
3038  [[binderClass getBinding:@"sortDescriptors" forObject:self] reverseSetValueFor:@"sortDescriptors"];
3039 }
3040 
3044 - (CPArray)sortDescriptors
3045 {
3046  return _sortDescriptors;
3047 }
3048 
3052 - (id)_objectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
3053 {
3054  var tableColumnUID = [aTableColumn UID],
3055  tableColumnObjectValues = _objectValues[tableColumnUID];
3056 
3057  if (!tableColumnObjectValues)
3058  {
3059  tableColumnObjectValues = [];
3060  _objectValues[tableColumnUID] = tableColumnObjectValues;
3061  }
3062 
3063  var objectValue = tableColumnObjectValues[aRowIndex];
3064 
3065  // tableView:objectValueForTableColumn:row: is optional if content bindings are in place.
3066  if (objectValue === undefined)
3067  {
3068  if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_objectValueForTableColumn_row_)
3069  {
3070  objectValue = [_dataSource tableView:self objectValueForTableColumn:aTableColumn row:aRowIndex];
3071  tableColumnObjectValues[aRowIndex] = objectValue;
3072  }
3073  else if (![self infoForBinding:@"content"])
3074  {
3075  CPLog(@"no content binding established and data source " + [_dataSource description] + " does not implement tableView:objectValueForTableColumn:row:");
3076  }
3077  }
3078 
3079  return objectValue;
3080 }
3081 
3082 
3086 - (CGRect)exposedRect
3087 {
3088  if (!_exposedRect)
3089  {
3090  var superview = [self superview];
3091 
3092  // FIXME: Should we be rect intersecting in case
3093  // there are multiple views in the clip view?
3094  if ([superview isKindOfClass:[CPClipView class]])
3095  _exposedRect = [superview bounds];
3096 
3097  else
3098  _exposedRect = [self bounds];
3099  }
3100 
3101  return _exposedRect;
3102 }
3103 
3107 - (void)load
3108 {
3109  if (_reloadAllRows)
3110  {
3111  [self _unloadDataViewsInRows:_exposedRows columns:_exposedColumns];
3112 
3113  _exposedRows = [CPIndexSet indexSet];
3114  _exposedColumns = [CPIndexSet indexSet];
3115 
3116  _reloadAllRows = NO;
3117  }
3118 
3119  var exposedRect = [self exposedRect],
3120  exposedRows = [CPIndexSet indexSetWithIndexesInRange:[self rowsInRect:exposedRect]],
3121  exposedColumns = [self columnIndexesInRect:exposedRect],
3122  obscuredRows = [_exposedRows copy],
3123  obscuredColumns = [_exposedColumns copy];
3124 
3125  [obscuredRows removeIndexes:exposedRows];
3126  [obscuredColumns removeIndexes:exposedColumns];
3127 
3128  var newlyExposedRows = [exposedRows copy],
3129  newlyExposedColumns = [exposedColumns copy];
3130 
3131  [newlyExposedRows removeIndexes:_exposedRows];
3132  [newlyExposedColumns removeIndexes:_exposedColumns];
3133 
3134  var previouslyExposedRows = [exposedRows copy],
3135  previouslyExposedColumns = [exposedColumns copy];
3136 
3137  [previouslyExposedRows removeIndexes:newlyExposedRows];
3138  [previouslyExposedColumns removeIndexes:newlyExposedColumns];
3139 
3140  [self _unloadDataViewsInRows:previouslyExposedRows columns:obscuredColumns];
3141  [self _unloadDataViewsInRows:obscuredRows columns:previouslyExposedColumns];
3142  [self _unloadDataViewsInRows:obscuredRows columns:obscuredColumns];
3143  [self _unloadDataViewsInRows:newlyExposedRows columns:newlyExposedColumns];
3144 
3145  [self _loadDataViewsInRows:previouslyExposedRows columns:newlyExposedColumns];
3146  [self _loadDataViewsInRows:newlyExposedRows columns:previouslyExposedColumns];
3147  [self _loadDataViewsInRows:newlyExposedRows columns:newlyExposedColumns];
3148 
3149  _exposedRows = exposedRows;
3150  _exposedColumns = exposedColumns;
3151 
3152  [_tableDrawView setFrame:exposedRect];
3153 
3154  [self setNeedsDisplay:YES];
3155 
3156  // Now clear all the leftovers
3157  // FIXME: this could be faster!
3158  for (var identifier in _cachedDataViews)
3159  {
3160  var dataViews = _cachedDataViews[identifier],
3161  count = dataViews.length;
3162 
3163  while (count--)
3164  [dataViews[count] removeFromSuperview];
3165  }
3166 
3167  // if we have any columns to remove do that here
3168  if ([_differedColumnDataToRemove count])
3169  {
3170  for (var i = 0; i < _differedColumnDataToRemove.length; i++)
3171  {
3172  var data = _differedColumnDataToRemove[i],
3173  column = data.column;
3174 
3175  [column setHidden:data.shouldBeHidden];
3176  [_tableColumns removeObject:column];
3177  }
3178  [_differedColumnDataToRemove removeAllObjects];
3179  }
3180 
3181 }
3182 
3186 - (void)_unloadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
3187 {
3188  if (![rows count] || ![columns count])
3189  return;
3190 
3191  var rowArray = [],
3192  columnArray = [];
3193 
3194  [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
3195  [columns getIndexes:columnArray maxCount:-1 inIndexRange:nil];
3196 
3197  var columnIndex = 0,
3198  columnsCount = columnArray.length;
3199 
3200  for (; columnIndex < columnsCount; ++columnIndex)
3201  {
3202  var column = columnArray[columnIndex],
3203  tableColumn = _tableColumns[column],
3204  tableColumnUID = [tableColumn UID],
3205  rowIndex = 0,
3206  rowsCount = rowArray.length;
3207 
3208  for (; rowIndex < rowsCount; ++rowIndex)
3209  {
3210  var row = rowArray[rowIndex],
3211  dataViews = _dataViewsForTableColumns[tableColumnUID];
3212 
3213  if (!dataViews || row >= dataViews.length)
3214  continue;
3215 
3216  var dataView = [dataViews objectAtIndex:row];
3217 
3218  [dataViews replaceObjectAtIndex:row withObject:nil];
3219 
3220  [self _enqueueReusableDataView:dataView];
3221  }
3222  }
3223 }
3224 
3228 - (void)_loadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
3229 {
3230  if (![rows count] || ![columns count])
3231  return;
3232 
3233  var rowArray = [],
3234  rowRects = [],
3235  columnArray = [];
3236 
3237  [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
3238  [columns getIndexes:columnArray maxCount:-1 inIndexRange:nil];
3239 
3241 
3242  var columnIndex = 0,
3243  columnsCount = columnArray.length;
3244 
3245  for (; columnIndex < columnsCount; ++columnIndex)
3246  {
3247  var column = columnArray[columnIndex],
3248  tableColumn = _tableColumns[column];
3249 
3250  if ([tableColumn isHidden] || tableColumn === _draggedColumn)
3251  continue;
3252 
3253  var tableColumnUID = [tableColumn UID];
3254 
3255  if (!_dataViewsForTableColumns[tableColumnUID])
3256  _dataViewsForTableColumns[tableColumnUID] = [];
3257 
3258  var rowIndex = 0,
3259  rowsCount = rowArray.length,
3260  isColumnSelected = [_selectedColumnIndexes containsIndex:column];
3261 
3262  for (; rowIndex < rowsCount; ++rowIndex)
3263  {
3264  var row = rowArray[rowIndex],
3265  dataView = [self _newDataViewForRow:row tableColumn:tableColumn],
3266  isButton = [dataView isKindOfClass:[CPButton class]],
3267  isTextField = [dataView isKindOfClass:[CPTextField class]];
3268 
3269  [dataView setFrame:[self frameOfDataViewAtColumn:column row:row]];
3270 
3271  [self _setObjectValueForTableColumn:tableColumn row:row forView:dataView];
3272 
3273  if (isColumnSelected || [self isRowSelected:row])
3274  [dataView setThemeState:CPThemeStateSelectedDataView];
3275  else
3276  [dataView unsetThemeState:CPThemeStateSelectedDataView];
3277 
3278  // FIX ME: for performance reasons we might consider diverging from cocoa and moving this to the reloadData method
3279  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_isGroupRow_)
3280  {
3281  if ([_delegate tableView:self isGroupRow:row])
3282  {
3283  [_groupRows addIndex:row];
3284  [dataView setThemeState:CPThemeStateGroupRow];
3285  }
3286  else
3287  {
3288  [_groupRows removeIndexesInRange:CPMakeRange(row, 1)];
3289  [dataView unsetThemeState:CPThemeStateGroupRow];
3290  }
3291 
3292  [self setNeedsDisplay:YES]
3293  }
3294 
3296  [_delegate tableView:self willDisplayView:dataView forTableColumn:tableColumn row:row];
3297 
3298  if ([dataView superview] !== self)
3299  [self addSubview:dataView];
3300 
3301  _dataViewsForTableColumns[tableColumnUID][row] = dataView;
3302 
3303  if (isButton || (_editingCellIndex && _editingCellIndex.x === column && _editingCellIndex.y === row))
3304  {
3305  if (isTextField)
3306  {
3307  [dataView setEditable:YES];
3308  [dataView setSendsActionOnEndEditing:YES];
3309  [dataView setSelectable:YES];
3310  [dataView selectText:nil];
3311  [dataView setBezeled:YES];
3312  [dataView setDelegate:self];
3313  }
3314 
3315  [dataView setTarget:self];
3316  [dataView setAction:@selector(_commitDataViewObjectValue:)];
3317  dataView.tableViewEditedColumnObj = tableColumn;
3318  dataView.tableViewEditedRowIndex = row;
3319  }
3320  else if (isTextField)
3321  {
3322  [dataView setEditable:NO];
3323  [dataView setSelectable:NO];
3324  }
3325  }
3326  }
3327 }
3328 
3329 - (void)_setObjectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow forView:(CPView)aDataView
3330 {
3331  if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_objectValueForTableColumn_row_)
3332  [aDataView setObjectValue:[self _objectValueForTableColumn:aTableColumn row:aRow]];
3333 
3334  // This gives the table column an opportunity to apply its bindings.
3335  // It will override the value set above if there is a binding.
3336 
3337  if (_contentBindingExpicitelySet)
3338  [self _prepareContentBindedDataView:aDataView forRow:aRow];
3339  else
3340  // For both cell-based and view-based
3341  [aTableColumn _prepareDataView:aDataView forRow:aRow];
3342 }
3343 
3344 - (void)_prepareContentBindedDataView:(CPView)dataView forRow:(CPInteger)aRow
3345 {
3346  var binder = [CPTableContentBinder getBinding:@"content" forObject:self],
3347  content = [binder content],
3348  rowContent = [content objectAtIndex:aRow];
3349 
3350  [dataView setObjectValue:rowContent];
3351 }
3352 
3356 - (void)_layoutDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
3357 {
3358  var rowArray = [],
3359  columnArray = [];
3360 
3361  [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
3362  [columns getIndexes:columnArray maxCount:-1 inIndexRange:nil];
3363 
3364  var columnIndex = 0,
3365  columnsCount = columnArray.length;
3366 
3367  for (; columnIndex < columnsCount; ++columnIndex)
3368  {
3369  var column = columnArray[columnIndex],
3370  tableColumn = _tableColumns[column],
3371  tableColumnUID = [tableColumn UID],
3372  dataViewsForTableColumn = _dataViewsForTableColumns[tableColumnUID],
3373  rowIndex = 0,
3374  rowsCount = rowArray.length;
3375 
3376  if (dataViewsForTableColumn)
3377  {
3378  for (; rowIndex < rowsCount; ++rowIndex)
3379  {
3380  var row = rowArray[rowIndex],
3381  dataView = dataViewsForTableColumn[row];
3382 
3383  [dataView setFrame:[self frameOfDataViewAtColumn:column row:row]];
3384  }
3385  }
3386  }
3387 }
3388 
3394 - (void)_commitDataViewObjectValue:(id)sender
3395 {
3396  /*
3397  makeFirstResponder at the end of this method causes the dataview to resign.
3398  If the dataview resigning triggers the action (as CPTextField does), we come right
3399  back here and start an infinite loop. So we have to check this flag first.
3400  */
3401  if ([sender respondsToSelector:@selector(sendsActionOnEndEditing)] && [sender sendsActionOnEndEditing] && _editingCellIndex === nil)
3402  return;
3403 
3404  _editingCellIndex = nil;
3405 
3406  if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_)
3407  [_dataSource tableView:self setObjectValue:[sender objectValue] forTableColumn:sender.tableViewEditedColumnObj row:sender.tableViewEditedRowIndex];
3408 
3409  // Allow the column binding to do a reverse set. Note that we do this even if the data source method above
3410  // is implemented.
3411  [sender.tableViewEditedColumnObj _reverseSetDataView:sender forRow:sender.tableViewEditedRowIndex];
3412 
3413  if ([sender respondsToSelector:@selector(setEditable:)])
3414  [sender setEditable:NO];
3415 
3416  if ([sender respondsToSelector:@selector(setSelectable:)])
3417  [sender setSelectable:NO];
3418 
3419  if ([sender isKindOfClass:[CPTextField class]])
3420  [sender setBezeled:NO];
3421 
3422  [self reloadDataForRowIndexes:[CPIndexSet indexSetWithIndex:sender.tableViewEditedRowIndex]
3423  columnIndexes:[CPIndexSet indexSetWithIndex:[_tableColumns indexOfObject:sender.tableViewEditedColumnObj]]];
3424 
3425  [[self window] makeFirstResponder:self];
3426 
3427 }
3428 
3434 - (void)controlTextDidBlur:(CPNotification)theNotification
3435 {
3436  var dataView = [theNotification object];
3437 
3438  if ([dataView respondsToSelector:@selector(setEditable:)])
3439  [dataView setEditable:NO];
3440 
3441  if ([dataView respondsToSelector:@selector(setSelectable:)])
3442  [dataView setSelectable:NO];
3443 
3444  if ([dataView isKindOfClass:[CPTextField class]])
3445  [dataView setBezeled:NO];
3446 
3447  _editingCellIndex = nil;
3448 }
3449 
3453 - (CPView)_newDataViewForRow:(CPInteger)aRow tableColumn:(CPTableColumn)aTableColumn
3454 {
3455  if ((_implementedDelegateMethods & CPTableViewDelegate_tableView_dataViewForTableColumn_row_))
3456  {
3457  var dataView = [_delegate tableView:self dataViewForTableColumn:aTableColumn row:aRow];
3458  [aTableColumn setDataView:dataView];
3459  }
3460 
3461  return [aTableColumn _newDataViewForRow:aRow];
3462 }
3463 
3467 - (void)_enqueueReusableDataView:(CPView)aDataView
3468 {
3469  if (!aDataView)
3470  return;
3471 
3472  // FIXME: yuck!
3473  var identifier = aDataView.identifier;
3474 
3475  if (!_cachedDataViews[identifier])
3476  _cachedDataViews[identifier] = [aDataView];
3477  else
3478  _cachedDataViews[identifier].push(aDataView);
3479 }
3480 
3485 - (void)setFrameSize:(CGSize)aSize
3486 {
3487  [super setFrameSize:aSize];
3488 
3489  if (_headerView)
3490  [_headerView setFrameSize:_CGSizeMake(_CGRectGetWidth([self frame]), _CGRectGetHeight([_headerView frame]))];
3491 
3492  _exposedRect = nil;
3493 }
3494 
3498 - (void)setFrameOrigin:(CGPoint)aPoint
3499 {
3500  [super setFrameOrigin:aPoint];
3501 
3502  _exposedRect = nil;
3503 }
3504 
3508 - (void)setBoundsOrigin:(CGPoint)aPoint
3509 {
3510  [super setBoundsOrigin:aPoint];
3511 
3512  _exposedRect = nil;
3513 }
3514 
3518 - (void)setBoundsSize:(CGSize)aSize
3519 {
3520  [super setBoundsSize:aSize];
3521 
3522  _exposedRect = nil;
3523 }
3524 
3528 - (void)setNeedsDisplay:(BOOL)aFlag
3529 {
3530  [super setNeedsDisplay:aFlag];
3531  [_tableDrawView setNeedsDisplay:aFlag];
3532 
3533  [[self headerView] setNeedsDisplay:YES];
3534 }
3535 
3539 - (void)setNeedsLayout
3540 {
3541  [super setNeedsLayout];
3542  [[self headerView] setNeedsLayout];
3543 }
3544 
3545 
3549 - (void)_drawRect:(CGRect)aRect
3550 {
3551  // FIX ME: All three of these methods will likely need to be rewritten for 1.0
3552  // We've got grid drawing in highlightSelection and crap everywhere.
3553 
3554  var exposedRect = [self exposedRect];
3555 
3556  [self drawBackgroundInClipRect:exposedRect];
3557  [self highlightSelectionInClipRect:exposedRect];
3558  [self drawGridInClipRect:exposedRect];
3559 
3560  if (_implementsCustomDrawRow)
3561  [self _drawRows:_exposedRows clipRect:exposedRect];
3562 }
3563 
3569 - (void)drawBackgroundInClipRect:(CGRect)aRect
3570 {
3571  if (!_usesAlternatingRowBackgroundColors)
3572  return;
3573 
3574  var rowColors = [self alternatingRowBackgroundColors],
3575  colorCount = [rowColors count];
3576 
3577  if (colorCount === 0)
3578  return;
3579 
3580  var context = [[CPGraphicsContext currentContext] graphicsPort];
3581 
3582  if (colorCount === 1)
3583  {
3584  CGContextSetFillColor(context, rowColors[0]);
3585  CGContextFillRect(context, aRect);
3586 
3587  return;
3588  }
3589 
3590  var exposedRows = [self _unboundedRowsInRect:aRect],
3591  firstRow = FLOOR(exposedRows.location / colorCount) * colorCount,
3592  lastRow = CPMaxRange(exposedRows),
3593  colorIndex = 0,
3594  groupRowRects = [];
3595 
3596  //loop through each color so we only draw once for each color
3597  while (colorIndex < colorCount)
3598  {
3599  CGContextBeginPath(context);
3600  for (var row = firstRow + colorIndex; row <= lastRow; row += colorCount)
3601  {
3602  // if it's not a group row draw it otherwise we draw it later
3603  if (![_groupRows containsIndex:row])
3604  CGContextAddRect(context, CGRectIntersection(aRect, [self _rectOfRow:row checkRange:NO]));
3605  else
3606  groupRowRects.push(CGRectIntersection(aRect, [self _rectOfRow:row checkRange:NO]));
3607  }
3608  CGContextClosePath(context);
3609 
3610  CGContextSetFillColor(context, rowColors[colorIndex]);
3611  CGContextFillPath(context);
3612 
3613  colorIndex++;
3614  }
3615 
3616  [self _drawGroupRowsForRects:groupRowRects];
3617 }
3618 
3623 - (void)drawGridInClipRect:(CGRect)aRect
3624 {
3625  var context = [[CPGraphicsContext currentContext] graphicsPort],
3626  gridStyleMask = [self gridStyleMask];
3627 
3629  return;
3630 
3631  CGContextBeginPath(context);
3632 
3633  if (gridStyleMask & CPTableViewSolidHorizontalGridLineMask)
3634  {
3635  var exposedRows = [self _unboundedRowsInRect:aRect],
3636  row = exposedRows.location,
3637  lastRow = CPMaxRange(exposedRows) - 1,
3638  rowY = -0.5,
3639  minX = _CGRectGetMinX(aRect),
3640  maxX = _CGRectGetMaxX(aRect);
3641 
3642  for (; row <= lastRow; ++row)
3643  {
3644  // grab each row rect and add the top and bottom lines
3645  var rowRect = [self _rectOfRow:row checkRange:NO],
3646  rowY = _CGRectGetMaxY(rowRect) - 0.5;
3647 
3648  CGContextMoveToPoint(context, minX, rowY);
3649  CGContextAddLineToPoint(context, maxX, rowY);
3650  }
3651 
3652  if (_rowHeight > 0.0)
3653  {
3654  var rowHeight = _rowHeight + _intercellSpacing.height,
3655  totalHeight = _CGRectGetMaxY(aRect);
3656 
3657  while (rowY < totalHeight)
3658  {
3659  rowY += rowHeight;
3660 
3661  CGContextMoveToPoint(context, minX, rowY);
3662  CGContextAddLineToPoint(context, maxX, rowY);
3663  }
3664  }
3665  }
3666 
3667  if (gridStyleMask & CPTableViewSolidVerticalGridLineMask)
3668  {
3669  var exposedColumnIndexes = [self columnIndexesInRect:aRect],
3670  columnsArray = [];
3671 
3672  [exposedColumnIndexes getIndexes:columnsArray maxCount:-1 inIndexRange:nil];
3673 
3674  var columnArrayIndex = 0,
3675  columnArrayCount = columnsArray.length,
3676  minY = _CGRectGetMinY(aRect),
3677  maxY = _CGRectGetMaxY(aRect);
3678 
3679  for (; columnArrayIndex < columnArrayCount; ++columnArrayIndex)
3680  {
3681  var columnRect = [self rectOfColumn:columnsArray[columnArrayIndex]],
3682  columnX = _CGRectGetMaxX(columnRect) - 0.5;
3683 
3684  CGContextMoveToPoint(context, columnX, minY);
3685  CGContextAddLineToPoint(context, columnX, maxY);
3686  }
3687  }
3688 
3689  CGContextClosePath(context);
3690  CGContextSetStrokeColor(context, [self gridColor]);
3691  CGContextStrokePath(context);
3692 }
3693 
3699 - (void)highlightSelectionInClipRect:(CGRect)aRect
3700 {
3701  if (_selectionHighlightStyle === CPTableViewSelectionHighlightStyleNone)
3702  return;
3703 
3704  var context = [[CPGraphicsContext currentContext] graphicsPort],
3705  indexes = [],
3706  rectSelector = @selector(rectOfRow:);
3707 
3708  if ([_selectedRowIndexes count] >= 1)
3709  {
3710  var exposedRows = [CPIndexSet indexSetWithIndexesInRange:[self rowsInRect:aRect]],
3711  firstRow = [exposedRows firstIndex],
3712  exposedRange = CPMakeRange(firstRow, [exposedRows lastIndex] - firstRow + 1);
3713 
3714  [_selectedRowIndexes getIndexes:indexes maxCount:-1 inIndexRange:exposedRange];
3715  }
3716  else if ([_selectedColumnIndexes count] >= 1)
3717  {
3718  rectSelector = @selector(rectOfColumn:);
3719 
3720  var exposedColumns = [self columnIndexesInRect:aRect],
3721  firstColumn = [exposedColumns firstIndex],
3722  exposedRange = CPMakeRange(firstColumn, [exposedColumns lastIndex] - firstColumn + 1);
3723 
3724  [_selectedColumnIndexes getIndexes:indexes maxCount:-1 inIndexRange:exposedRange];
3725  }
3726 
3727  var count,
3728  count2 = count = [indexes count];
3729 
3730  if (!count)
3731  return;
3732 
3733  var drawGradient = (CPFeatureIsCompatible(CPHTMLCanvasFeature) && _selectionHighlightStyle === CPTableViewSelectionHighlightStyleSourceList && [_selectedRowIndexes count] >= 1),
3734  deltaHeight = 0.5 * (_gridStyleMask & CPTableViewSolidHorizontalGridLineMask);
3735 
3736  CGContextBeginPath(context);
3737 
3738  if (drawGradient)
3739  {
3740  var gradientCache = [self selectionGradientColors],
3741  topLineColor = [gradientCache objectForKey:CPSourceListTopLineColor],
3742  bottomLineColor = [gradientCache objectForKey:CPSourceListBottomLineColor],
3743  gradientColor = [gradientCache objectForKey:CPSourceListGradient];
3744  }
3745 
3746  var normalSelectionHighlightColor = [self selectionHighlightColor];
3747 
3748  // don't do these lookups if there are no group rows
3749  if ([_groupRows count])
3750  {
3751  var topGroupLineColor = [CPColor colorWithCalibratedWhite:212.0 / 255.0 alpha:1.0],
3752  bottomGroupLineColor = [CPColor colorWithCalibratedWhite:185.0 / 255.0 alpha:1.0],
3753  gradientGroupColor = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(), [212.0 / 255.0, 212.0 / 255.0, 212.0 / 255.0, 1.0, 197.0 / 255.0, 197.0 / 255.0, 197.0 / 255.0, 1.0], [0, 1], 2);
3754  }
3755 
3756  while (count--)
3757  {
3758  var currentIndex = indexes[count],
3759  rowRect = CGRectIntersection(objj_msgSend(self, rectSelector, currentIndex), aRect);
3760 
3761  // group rows get the same highlight style as other rows if they're source list...
3762  if (!drawGradient)
3763  var shouldUseGroupGradient = [_groupRows containsIndex:currentIndex];
3764 
3765  if (drawGradient || shouldUseGroupGradient)
3766  {
3767  var minX = _CGRectGetMinX(rowRect),
3768  minY = _CGRectGetMinY(rowRect),
3769  maxX = _CGRectGetMaxX(rowRect),
3770  maxY = _CGRectGetMaxY(rowRect) - deltaHeight;
3771 
3772  if (!drawGradient)
3773  {
3774  //If there is no source list gradient we need to close the selection path and fill it now
3775  [normalSelectionHighlightColor setFill];
3776  CGContextClosePath(context);
3777  CGContextFillPath(context);
3778  CGContextBeginPath(context);
3779  }
3780  CGContextAddRect(context, rowRect);
3781 
3782  CGContextDrawLinearGradient(context, (shouldUseGroupGradient) ? gradientGroupColor : gradientColor, rowRect.origin, _CGPointMake(minX, maxY), 0);
3783  CGContextClosePath(context);
3784 
3785  CGContextBeginPath(context);
3786  CGContextMoveToPoint(context, minX, minY + .5);
3787  CGContextAddLineToPoint(context, maxX, minY + .5);
3788  CGContextClosePath(context);
3789  CGContextSetStrokeColor(context, (shouldUseGroupGradient) ? topGroupLineColor : topLineColor);
3790  CGContextStrokePath(context);
3791 
3792  CGContextBeginPath(context);
3793  CGContextMoveToPoint(context, minX, maxY - .5);
3794  CGContextAddLineToPoint(context, maxX, maxY - .5);
3795  CGContextClosePath(context);
3796  CGContextSetStrokeColor(context, (shouldUseGroupGradient) ? bottomGroupLineColor : bottomLineColor);
3797  CGContextStrokePath(context);
3798  }
3799  else
3800  {
3801  var radius = [self currentValueForThemeAttribute:@"selection-radius"];
3802 
3803  if (radius > 0)
3804  {
3805  var minX = CGRectGetMinX(rowRect),
3806  maxX = CGRectGetMaxX(rowRect),
3807  minY = CGRectGetMinY(rowRect),
3808  maxY = CGRectGetMaxY(rowRect);
3809 
3810  CGContextMoveToPoint(context, minX + radius, minY);
3811  CGContextAddArcToPoint(context, maxX, minY, maxX, minY + radius, radius);
3812  CGContextAddArcToPoint(context, maxX, maxY, maxX - radius, maxY, radius);
3813  CGContextAddArcToPoint(context, minX, maxY, minX, maxY - radius, radius);
3814  CGContextAddArcToPoint(context, minX, minY, minX + radius, minY, radius);
3815  }
3816  else
3817  CGContextAddRect(context, rowRect);
3818  }
3819  }
3820 
3821  CGContextClosePath(context);
3822 
3823  if (!drawGradient)
3824  {
3825  [normalSelectionHighlightColor setFill];
3826  CGContextFillPath(context);
3827  }
3828 
3829  CGContextBeginPath(context);
3830  var gridStyleMask = [self gridStyleMask];
3831  for (var i = 0; i < count2; i++)
3832  {
3833  var rect = objj_msgSend(self, rectSelector, indexes[i]),
3834  minX = _CGRectGetMinX(rect) - 0.5,
3835  maxX = _CGRectGetMaxX(rect) - 0.5,
3836  minY = _CGRectGetMinY(rect) - 0.5,
3837  maxY = _CGRectGetMaxY(rect) - 0.5;
3838 
3839  if ([_selectedRowIndexes count] >= 1 && gridStyleMask & CPTableViewSolidVerticalGridLineMask)
3840  {
3841  var exposedColumns = [self columnIndexesInRect:aRect],
3842  exposedColumnIndexes = [],
3843  firstExposedColumn = [exposedColumns firstIndex],
3844  exposedRange = CPMakeRange(firstExposedColumn, [exposedColumns lastIndex] - firstExposedColumn + 1);
3845  [exposedColumns getIndexes:exposedColumnIndexes maxCount:-1 inIndexRange:exposedRange];
3846  var exposedColumnCount = [exposedColumnIndexes count];
3847 
3848  for (var c = firstExposedColumn; c < exposedColumnCount; c++)
3849  {
3850  var colRect = [self rectOfColumn:exposedColumnIndexes[c]],
3851  colX = _CGRectGetMaxX(colRect) + 0.5;
3852 
3853  CGContextMoveToPoint(context, colX, minY);
3854  CGContextAddLineToPoint(context, colX, maxY);
3855  }
3856  }
3857 
3858  //if the row after the current row is not selected then there is no need to draw the bottom grid line white.
3859  if ([indexes containsObject:indexes[i] + 1])
3860  {
3861  CGContextMoveToPoint(context, minX, maxY);
3862  CGContextAddLineToPoint(context, maxX, maxY);
3863  }
3864  }
3865 
3866  CGContextClosePath(context);
3867  CGContextSetStrokeColor(context, [self currentValueForThemeAttribute:@"highlighted-grid-color"]);
3868  CGContextStrokePath(context);
3869 }
3870 
3876 - (void)_drawGroupRowsForRects:(CPArray)rects
3877 {
3878  if ((CPFeatureIsCompatible(CPHTMLCanvasFeature) && _selectionHighlightStyle === CPTableViewSelectionHighlightStyleSourceList) || !rects.length)
3879  return;
3880 
3881  var context = [[CPGraphicsContext currentContext] graphicsPort],
3882  i = rects.length;
3883 
3884  CGContextBeginPath(context);
3885 
3886  var gradientCache = [self selectionGradientColors],
3887  topLineColor = [CPColor colorWithHexString:"d3d3d3"],
3888  bottomLineColor = [CPColor colorWithHexString:"bebebd"],
3889  gradientColor = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(), [220.0 / 255.0, 220.0 / 255.0, 220.0 / 255.0, 1.0,
3890  199.0 / 255.0, 199.0 / 255.0, 199.0 / 255.0, 1.0], [0, 1], 2),
3891  drawGradient = YES;
3892 
3893  while (i--)
3894  {
3895  var rowRect = rects[i];
3896 
3897  CGContextAddRect(context, rowRect);
3898 
3899  if (drawGradient)
3900  {
3901  var minX = CGRectGetMinX(rowRect),
3902  minY = CGRectGetMinY(rowRect),
3903  maxX = CGRectGetMaxX(rowRect),
3904  maxY = CGRectGetMaxY(rowRect);
3905 
3906  CGContextDrawLinearGradient(context, gradientColor, rowRect.origin, CGPointMake(minX, maxY), 0);
3907  CGContextClosePath(context);
3908 
3909  CGContextBeginPath(context);
3910  CGContextMoveToPoint(context, minX, minY);
3911  CGContextAddLineToPoint(context, maxX, minY);
3912  CGContextClosePath(context);
3913  CGContextSetStrokeColor(context, topLineColor);
3914  CGContextStrokePath(context);
3915 
3916  CGContextBeginPath(context);
3917  CGContextMoveToPoint(context, minX, maxY);
3918  CGContextAddLineToPoint(context, maxX, maxY - 1);
3919  CGContextClosePath(context);
3920  CGContextSetStrokeColor(context, bottomLineColor);
3921  CGContextStrokePath(context);
3922  }
3923  }
3924 
3925  CGContextClosePath(context);
3926 }
3927 
3931 - (void)_drawRows:(CPIndexSet)rowsIndexes clipRect:(CGRect)clipRect
3932 {
3933  var row = [rowsIndexes firstIndex];
3934 
3935  while (row !== CPNotFound)
3936  {
3937  [self drawRow:row clipRect:CGRectIntersection(clipRect, [self rectOfRow:row])];
3938  row = [rowsIndexes indexGreaterThanIndex:row];
3939  }
3940 }
3941 
3948 - (void)drawRow:(CPInteger)row clipRect:(CGRect)rect
3949 {
3950  // This method does currently nothing in cappuccino. Can be overridden by subclasses.
3951 
3952 }
3953 
3957 - (void)layoutSubviews
3958 {
3959  [self load];
3960 }
3961 
3965 - (void)viewWillMoveToSuperview:(CPView)aView
3966 {
3967  var superview = [self superview],
3968  defaultCenter = [CPNotificationCenter defaultCenter];
3969 
3970  if (superview)
3971  {
3972  [defaultCenter
3973  removeObserver:self
3974  name:CPViewFrameDidChangeNotification
3975  object:superview];
3976 
3977  [defaultCenter
3978  removeObserver:self
3979  name:CPViewBoundsDidChangeNotification
3980  object:superview];
3981  }
3982 
3983  if ([aView isKindOfClass:[CPClipView class]])
3984  {
3987 
3988  [defaultCenter
3989  addObserver:self
3990  selector:@selector(superviewFrameChanged:)
3991  name:CPViewFrameDidChangeNotification
3992  object:aView];
3993 
3994  [defaultCenter
3995  addObserver:self
3996  selector:@selector(superviewBoundsChanged:)
3997  name:CPViewBoundsDidChangeNotification
3998  object:aView];
3999  }
4000 }
4001 
4005 - (void)superviewBoundsChanged:(CPNotification)aNotification
4006 {
4007  _exposedRect = nil;
4008 
4009  [self setNeedsDisplay:YES];
4010  [self setNeedsLayout];
4011 }
4012 
4016 - (void)superviewFrameChanged:(CPNotification)aNotification
4017 {
4018  _exposedRect = nil;
4019 
4020  [self tile];
4021 }
4022 
4023 /*
4024  @ignore
4025 */
4026 - (BOOL)tracksMouseOutsideOfFrame
4027 {
4028  return YES;
4029 }
4030 
4031 /*
4032  @ignore
4033 */
4034 - (BOOL)startTrackingAt:(CGPoint)aPoint
4035 {
4036  // Try to become the first responder, but if we can't, that's okay.
4037  [[self window] makeFirstResponder:self];
4038 
4039  var row = [self rowAtPoint:aPoint];
4040 
4041  // If the user clicks outside a row then deselect everything.
4042  if (row < 0 && _allowsEmptySelection)
4044 
4045  [self _noteSelectionIsChanging];
4046 
4047  if ([self mouseDownFlags] & CPShiftKeyMask)
4048  _selectionAnchorRow = (ABS([_selectedRowIndexes firstIndex] - row) < ABS([_selectedRowIndexes lastIndex] - row)) ?
4049  [_selectedRowIndexes firstIndex] : [_selectedRowIndexes lastIndex];
4050  else
4051  _selectionAnchorRow = row;
4052 
4053 
4054  //set ivars for startTrackingPoint and time...
4055  _startTrackingPoint = aPoint;
4056  _startTrackingTimestamp = new Date();
4057 
4058  if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_)
4059  _trackingPointMovedOutOfClickSlop = NO;
4060 
4061  // if the table has drag support then we use mouseUp to select a single row.
4062  // otherwise it uses mouse down.
4063  if (row >= 0 && !(_implementedDataSourceMethods & CPTableViewDataSource_tableView_writeRowsWithIndexes_toPasteboard_))
4064  [self _updateSelectionWithMouseAtRow:row];
4065 
4066  return YES;
4067 }
4068 
4072 - (CPMenu)menuForEvent:(CPEvent)theEvent
4073 {
4074  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableViewMenuForTableColumn_Row_))
4075  return [super menuForEvent:theEvent];
4076 
4077  var location = [self convertPoint:[theEvent locationInWindow] fromView:nil],
4078  row = [self rowAtPoint:location],
4079  column = [self columnAtPoint:location],
4080  tableColumn = [[self tableColumns] objectAtIndex:column];
4081 
4082  return [_delegate tableView:self menuForTableColumn:tableColumn row:row];
4083 }
4084 
4085 /*
4086  @ignore
4087 */
4088 - (void)trackMouse:(CPEvent)anEvent
4089 {
4090  // Prevent CPControl from eating the mouse events when we are in a drag session
4091  if (![_draggedRowIndexes count])
4092  {
4093  [self autoscroll:anEvent];
4094  [super trackMouse:anEvent];
4095  }
4096  else
4097  [CPApp sendEvent:anEvent];
4098 }
4099 
4100 /*
4101  @ignore
4102 */
4103 - (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
4104 {
4105  var row = [self rowAtPoint:aPoint];
4106 
4107  // begin the drag is the datasource lets us, we've move at least +-3px vertical or horizontal,
4108  // or we're dragging from selected rows and we haven't begun a drag session
4109  if (!_isSelectingSession && _implementedDataSourceMethods & CPTableViewDataSource_tableView_writeRowsWithIndexes_toPasteboard_)
4110  {
4111  if (row >= 0 && (ABS(_startTrackingPoint.x - aPoint.x) > 3 || (_verticalMotionCanDrag && ABS(_startTrackingPoint.y - aPoint.y) > 3)) ||
4112  ([_selectedRowIndexes containsIndex:row]))
4113  {
4114  if ([_selectedRowIndexes containsIndex:row])
4115  _draggedRowIndexes = [[CPIndexSet alloc] initWithIndexSet:_selectedRowIndexes];
4116  else
4117  _draggedRowIndexes = [CPIndexSet indexSetWithIndex:row];
4118 
4119 
4120  //ask the datasource for the data
4121  var pboard = [CPPasteboard pasteboardWithName:CPDragPboard];
4122 
4123  if ([self canDragRowsWithIndexes:_draggedRowIndexes atPoint:aPoint] && [_dataSource tableView:self writeRowsWithIndexes:_draggedRowIndexes toPasteboard:pboard])
4124  {
4125  var currentEvent = [CPApp currentEvent],
4126  offset = CPPointMakeZero(),
4127  tableColumns = [_tableColumns objectsAtIndexes:_exposedColumns];
4128 
4129  // We deviate from the default Cocoa implementation here by asking for a view in stead of an image
4130  // We support both, but the view preferred over the image because we can mimic the rows we are dragging
4131  // by re-creating the data views for the dragged rows
4132  var view = [self dragViewForRowsWithIndexes:_draggedRowIndexes
4133  tableColumns:tableColumns
4134  event:currentEvent
4135  offset:offset];
4136 
4137  if (!view)
4138  {
4139  var image = [self dragImageForRowsWithIndexes:_draggedRowIndexes
4140  tableColumns:tableColumns
4141  event:currentEvent
4142  offset:offset];
4143  view = [[CPImageView alloc] initWithFrame:CPMakeRect(0, 0, [image size].width, [image size].height)];
4144  [view setImage:image];
4145  }
4146 
4147  var bounds = [view bounds],
4148  viewLocation = CPPointMake(aPoint.x - CGRectGetWidth(bounds) / 2 + offset.x, aPoint.y - CGRectGetHeight(bounds) / 2 + offset.y);
4149  [self dragView:view at:viewLocation offset:CPPointMakeZero() event:[CPApp currentEvent] pasteboard:pboard source:self slideBack:YES];
4150  _startTrackingPoint = nil;
4151 
4152  return NO;
4153  }
4154 
4155  // The delegate disallowed the drag so clear the dragged row indexes
4156  _draggedRowIndexes = [CPIndexSet indexSet];
4157  }
4158  else if (ABS(_startTrackingPoint.x - aPoint.x) < 5 && ABS(_startTrackingPoint.y - aPoint.y) < 5)
4159  return YES;
4160  }
4161 
4162  _isSelectingSession = YES;
4163  if (row >= 0 && row !== _lastTrackedRowIndex)
4164  {
4165  _lastTrackedRowIndex = row;
4166  [self _updateSelectionWithMouseAtRow:row];
4167  }
4168 
4169  if ((_implementedDataSourceMethods & CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_)
4170  && !_trackingPointMovedOutOfClickSlop)
4171  {
4172  var CLICK_SPACE_DELTA = 5.0; // Stolen from AppKit/Platform/DOM/CPPlatformWindow+DOM.j
4173  if (ABS(aPoint.x - _startTrackingPoint.x) > CLICK_SPACE_DELTA
4174  || ABS(aPoint.y - _startTrackingPoint.y) > CLICK_SPACE_DELTA)
4175  {
4176  _trackingPointMovedOutOfClickSlop = YES;
4177  }
4178  }
4179 
4180  return YES;
4181 }
4182 
4186 - (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
4187 {
4188  _isSelectingSession = NO;
4189 
4190  var CLICK_TIME_DELTA = 1000,
4191  columnIndex = -1,
4192  column,
4193  rowIndex,
4194  shouldEdit = YES;
4195 
4196  if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_writeRowsWithIndexes_toPasteboard_)
4197  {
4198  rowIndex = [self rowAtPoint:aPoint];
4199 
4200  if (rowIndex !== -1)
4201  {
4202  if ([_draggedRowIndexes count] > 0)
4203  {
4204  _draggedRowIndexes = [CPIndexSet indexSet];
4205  return;
4206  }
4207  // if the table has drag support then we use mouseUp to select a single row.
4208  _previouslySelectedRowIndexes = [_selectedRowIndexes copy];
4209  [self _updateSelectionWithMouseAtRow:rowIndex];
4210  }
4211  }
4212 
4213  // Accept either tableView:setObjectValue:forTableColumn:row: delegate method, or a binding.
4214  if (mouseIsUp
4215  && !_trackingPointMovedOutOfClickSlop
4216  && ([[CPApp currentEvent] clickCount] > 1)
4217  && ((_implementedDataSourceMethods & CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_)
4218  || [self infoForBinding:@"content"]))
4219  {
4220  columnIndex = [self columnAtPoint:lastPoint];
4221 
4222  if (columnIndex !== -1)
4223  {
4224  column = _tableColumns[columnIndex];
4225 
4226  if ([column isEditable])
4227  {
4228  rowIndex = [self rowAtPoint:aPoint];
4229 
4230  if (rowIndex !== -1)
4231  {
4232  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldEditTableColumn_row_)
4233  shouldEdit = [_delegate tableView:self shouldEditTableColumn:column row:rowIndex];
4234  if (shouldEdit)
4235  {
4236  [self editColumn:columnIndex row:rowIndex withEvent:nil select:YES];
4237  return;
4238  }
4239  }
4240  }
4241  }
4242 
4243  } //end of editing conditional
4244 
4245  //double click actions
4246  if ([[CPApp currentEvent] clickCount] === 2 && _doubleAction)
4247  {
4248  _clickedRow = [self rowAtPoint:aPoint];
4249  _clickedColumn = [self columnAtPoint:lastPoint];
4250  [self sendAction:_doubleAction to:_target];
4251  }
4252 }
4253 
4254 /*
4255  @ignore
4256 */
4257 - (CPDragOperation)draggingEntered:(id)sender
4258 {
4259  var location = [self convertPoint:[sender draggingLocation] fromView:nil],
4260  dropOperation = [self _proposedDropOperationAtPoint:location],
4261  row = [self _proposedRowAtPoint:location];
4262 
4263  if (_retargetedDropRow !== nil)
4264  row = _retargetedDropRow;
4265 
4266  var draggedTypes = [self registeredDraggedTypes],
4267  count = [draggedTypes count],
4268  i = 0;
4269 
4270  for (; i < count; i++)
4271  {
4272  if ([[[sender draggingPasteboard] types] containsObject:[draggedTypes objectAtIndex: i]])
4273  return [self _validateDrop:sender proposedRow:row proposedDropOperation:dropOperation];
4274  }
4275 
4276  return CPDragOperationNone;
4277 }
4278 
4279 /*
4280  @ignore
4281 */
4282 - (void)draggingExited:(id)sender
4283 {
4284  [_dropOperationFeedbackView removeFromSuperview];
4285 }
4286 
4287 /*
4288  @ignore
4289 */
4290 - (void)draggingEnded:(id)sender
4291 {
4292  [self _draggingEnded];
4293 }
4294 
4298 - (void)_draggingEnded
4299 {
4300  _retargetedDropOperation = nil;
4301  _retargetedDropRow = nil;
4302  _draggedRowIndexes = [CPIndexSet indexSet];
4303  [_dropOperationFeedbackView removeFromSuperview];
4304 }
4305 
4306 /*
4307  @ignore
4308 */
4309 - (BOOL)wantsPeriodicDraggingUpdates
4310 {
4311  return YES;
4312 }
4313 
4314 /*
4315  @ignore
4316 */
4317 - (CPTableViewDropOperation)_proposedDropOperationAtPoint:(CGPoint)theDragPoint
4318 {
4319  if (_retargetedDropOperation !== nil)
4320  return _retargetedDropOperation;
4321 
4322  var row = [self _proposedRowAtPoint:theDragPoint],
4323  rowRect = [self rectOfRow:row];
4324 
4325  // If there is no (the default) or to little inter cell spacing we create some room for the CPTableViewDropAbove indicator
4326  // This probably doesn't work if the row height is smaller than or around 5.0
4327  if ([self intercellSpacing].height < 5.0)
4328  rowRect = CPRectInset(rowRect, 0.0, 5.0 - [self intercellSpacing].height);
4329 
4330  // If the altered row rect contains the drag point we show the drop on
4331  // We don't show the drop on indicator if we are dragging below the last row
4332  // in that case we always want to show the drop above indicator
4333  if (CGRectContainsPoint(rowRect, theDragPoint) && row < _numberOfRows)
4334  return CPTableViewDropOn;
4335 
4336  return CPTableViewDropAbove;
4337 }
4338 
4339 /*
4340  @ignore
4341 */
4342 - (CPInteger)_proposedRowAtPoint:(CGPoint)dragPoint
4343 {
4344  var row = [self rowAtPoint:dragPoint],
4345  // Determine if the mouse is currently closer to this row or the row below it
4346  lowerRow = row + 1,
4347  rect = [self rectOfRow:row],
4348  bottomPoint = _CGRectGetMaxY(rect),
4349  bottomThirty = bottomPoint - ((bottomPoint - _CGRectGetMinY(rect)) * 0.3),
4350  numberOfRows = [self numberOfRows];
4351 
4352  if (row < 0)
4353  row = (_CGRectGetMaxY(rect) < dragPoint.y) ? numberOfRows : row;
4354  else if (dragPoint.y > MAX(bottomThirty, bottomPoint - 6))
4355  row = lowerRow;
4356 
4357  row = MIN(numberOfRows, row);
4358 
4359  return row;
4360 }
4361 
4365 - (void)_validateDrop:(id)info proposedRow:(CPInteger)row proposedDropOperation:(CPTableViewDropOperation)dropOperation
4366 {
4368  return [_dataSource tableView:self validateDrop:info proposedRow:row proposedDropOperation:dropOperation];
4369 
4370  return CPDragOperationNone;
4371 }
4372 
4376 - (CPRect)_rectForDropHighlightViewOnRow:(int)theRowIndex
4377 {
4378  if (theRowIndex >= [self numberOfRows])
4379  theRowIndex = [self numberOfRows] - 1;
4380 
4381  return [self _rectOfRow:theRowIndex checkRange:NO];
4382 }
4383 
4387 - (CPRect)_rectForDropHighlightViewBetweenUpperRow:(int)theUpperRowIndex andLowerRow:(int)theLowerRowIndex offset:(CPPoint)theOffset
4388 {
4389  if (theLowerRowIndex > [self numberOfRows])
4390  theLowerRowIndex = [self numberOfRows];
4391 
4392  return [self _rectOfRow:theLowerRowIndex checkRange:NO];
4393 }
4394 
4398 - (CPDragOperation)draggingUpdated:(id)sender
4399 {
4400  _retargetedDropRow = nil;
4401  _retargetedDropOperation = nil;
4402 
4403  var location = [self convertPoint:[sender draggingLocation] fromView:nil],
4404  dropOperation = [self _proposedDropOperationAtPoint:location],
4405  numberOfRows = [self numberOfRows],
4406  row = [self _proposedRowAtPoint:location],
4407  dragOperation = [self _validateDrop:sender proposedRow:row proposedDropOperation:dropOperation];
4408 
4409  if (_retargetedDropRow !== nil)
4410  row = _retargetedDropRow;
4411  if (_retargetedDropOperation !== nil)
4412  dropOperation = _retargetedDropOperation;
4413 
4414 
4415  if (dropOperation === CPTableViewDropOn && row >= numberOfRows)
4416  row = numberOfRows - 1;
4417 
4418  var rect = _CGRectMakeZero();
4419 
4420  if (row === -1)
4421  rect = [self exposedRect];
4422 
4423  else if (dropOperation === CPTableViewDropAbove)
4424  rect = [self _rectForDropHighlightViewBetweenUpperRow:row - 1 andLowerRow:row offset:location];
4425 
4426  else
4427  rect = [self _rectForDropHighlightViewOnRow:row];
4428 
4429  [_dropOperationFeedbackView setDropOperation:row !== -1 ? dropOperation : CPDragOperationNone];
4430  [_dropOperationFeedbackView setHidden:(dragOperation == CPDragOperationNone)];
4431  [_dropOperationFeedbackView setFrame:rect];
4432  [_dropOperationFeedbackView setCurrentRow:row];
4433  [self addSubview:_dropOperationFeedbackView];
4434 
4435  return dragOperation;
4436 }
4437 
4438 /*
4439  @ignore
4440 */
4441 - (BOOL)prepareForDragOperation:(id)sender
4442 {
4443  // FIX ME: is there anything else that needs to happen here?
4444  // actual validation is called in dragginUpdated:
4445  [_dropOperationFeedbackView removeFromSuperview];
4446 
4448 }
4449 
4450 /*
4451  @ignore
4452 */
4453 - (BOOL)performDragOperation:(id)sender
4454 {
4455  var location = [self convertPoint:[sender draggingLocation] fromView:nil],
4456  operation = [self _proposedDropOperationAtPoint:location],
4457  row = _retargetedDropRow;
4458 
4459  if (row === nil)
4460  var row = [self _proposedRowAtPoint:location];
4461 
4462  return [_dataSource tableView:self acceptDrop:sender row:row dropOperation:operation];
4463 }
4464 
4465 /*
4466  @ignore
4467 */
4468 - (void)concludeDragOperation:(id)sender
4469 {
4470  [self reloadData];
4471 }
4472 
4473 /*
4474  This method is sent to the data source for convenience...
4475 */
4476 - (void)draggedImage:(CPImage)anImage endedAt:(CGPoint)aLocation operation:(CPDragOperation)anOperation
4477 {
4478  if ([_dataSource respondsToSelector:@selector(tableView:didEndDraggedImage:atPosition:operation:)])
4479  [_dataSource tableView:self didEndDraggedImage:anImage atPosition:aLocation operation:anOperation];
4480 }
4481 
4482 /*
4483  @ignore
4484  We're using this because we drag views instead of images so we can get the rows themselves to actually drag.
4485 */
4486 - (void)draggedView:(CPImage)aView endedAt:(CGPoint)aLocation operation:(CPDragOperation)anOperation
4487 {
4488  [self _draggingEnded];
4489  [self draggedImage:aView endedAt:aLocation operation:anOperation];
4490 }
4491 
4495 - (void)_updateSelectionWithMouseAtRow:(CPInteger)aRow
4496 {
4497  //check to make sure the row exists
4498  if (aRow < 0)
4499  return;
4500 
4501  var newSelection,
4502  shouldExtendSelection = NO;
4503  // If cmd/ctrl was held down XOR the old selection with the proposed selection
4504  if ([self mouseDownFlags] & (CPCommandKeyMask | CPControlKeyMask | CPAlternateKeyMask))
4505  {
4506  if ([_selectedRowIndexes containsIndex:aRow])
4507  {
4508  newSelection = [_selectedRowIndexes copy];
4509 
4510  [newSelection removeIndex:aRow];
4511  }
4512 
4513  else if (_allowsMultipleSelection)
4514  {
4515  newSelection = [_selectedRowIndexes copy];
4516 
4517  [newSelection addIndex:aRow];
4518  }
4519 
4520  else
4521  newSelection = [CPIndexSet indexSetWithIndex:aRow];
4522  }
4523  else if (_allowsMultipleSelection)
4524  {
4525  newSelection = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(MIN(aRow, _selectionAnchorRow), ABS(aRow - _selectionAnchorRow) + 1)];
4526  shouldExtendSelection = [self mouseDownFlags] & CPShiftKeyMask &&
4527  ((_lastSelectedRow == [_selectedRowIndexes lastIndex] && aRow > _lastSelectedRow) ||
4528  (_lastSelectedRow == [_selectedRowIndexes firstIndex] && aRow < _lastSelectedRow));
4529  }
4530  else if (aRow >= 0 && aRow < _numberOfRows)
4531  newSelection = [CPIndexSet indexSetWithIndex:aRow];
4532  else
4533  newSelection = [CPIndexSet indexSet];
4534 
4535  if ([newSelection isEqualToIndexSet:_selectedRowIndexes])
4536  return;
4537 
4538  if (_implementedDelegateMethods & CPTableViewDelegate_selectionShouldChangeInTableView_ &&
4539  ![_delegate selectionShouldChangeInTableView:self])
4540  return;
4541 
4543  newSelection = [_delegate tableView:self selectionIndexesForProposedSelection:newSelection];
4544 
4545  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldSelectRow_)
4546  {
4547  var indexArray = [];
4548 
4549  [newSelection getIndexes:indexArray maxCount:-1 inIndexRange:nil];
4550 
4551  var indexCount = indexArray.length;
4552 
4553  while (indexCount--)
4554  {
4555  var index = indexArray[indexCount];
4556 
4557  if (![_delegate tableView:self shouldSelectRow:index])
4558  [newSelection removeIndex:index];
4559  }
4560 
4561  // as per cocoa
4562  if ([newSelection count] === 0)
4563  return;
4564  }
4565 
4566  // if empty selection is not allowed and the new selection has nothing selected, abort
4567  if (!_allowsEmptySelection && [newSelection count] === 0)
4568  return;
4569 
4570  if ([newSelection isEqualToIndexSet:_selectedRowIndexes])
4571  return;
4572 
4573  [self selectRowIndexes:newSelection byExtendingSelection:shouldExtendSelection];
4574 
4575  _lastSelectedRow = [newSelection containsIndex:aRow] ? aRow : [newSelection lastIndex];
4576 }
4577 
4581 - (void)_noteSelectionIsChanging
4582 {
4584  postNotificationName:CPTableViewSelectionIsChangingNotification
4585  object:self
4586  userInfo:nil];
4587 }
4588 
4592 - (void)_noteSelectionDidChange
4593 {
4595  postNotificationName:CPTableViewSelectionDidChangeNotification
4596  object:self
4597  userInfo:nil];
4598 }
4599 
4603 - (BOOL)becomeFirstResponder
4604 {
4605  return YES;
4606 }
4607 
4611 - (BOOL)acceptsFirstResponder
4612 {
4613  return YES;
4614 }
4615 
4619 - (void)keyDown:(CPEvent)anEvent
4620 {
4621  var character = [anEvent charactersIgnoringModifiers],
4622  modifierFlags = [anEvent modifierFlags];
4623 
4624  // Check for the key events manually, as opposed to waiting for CPWindow to sent the actual action message
4625  // in _processKeyboardUIKey:, because we might not want to handle the arrow events.
4626  if (character === CPUpArrowFunctionKey || character === CPDownArrowFunctionKey)
4627  {
4628  // We're not interested in the arrow keys if there are no rows.
4629  // Technically we should also not be interested if we can't scroll,
4630  // but Cocoa doesn't handle that situation either.
4631  if ([self numberOfRows] !== 0)
4632  {
4633  [self _moveSelectionWithEvent:anEvent upward:(character === CPUpArrowFunctionKey)];
4634 
4635  return;
4636  }
4637  }
4638  else if (character === CPDeleteCharacter || character === CPDeleteFunctionKey)
4639  {
4640  // Don't call super if the delegate is interested in the delete key
4641  if ([self _sendDelegateDeleteKeyPressed])
4642  return;
4643  }
4644 
4645  [super keyDown:anEvent];
4646 }
4647 
4653 - (BOOL)_selectionIsBroken
4654 {
4655  return [self selectedRowIndexes]._ranges.length !== 1;
4656 }
4657 
4663 - (void)_moveSelectionWithEvent:(CPEvent)theEvent upward:(BOOL)shouldGoUpward
4664 {
4665  if (_implementedDelegateMethods & CPTableViewDelegate_selectionShouldChangeInTableView_ && ![_delegate selectionShouldChangeInTableView:self])
4666  return;
4667  var selectedIndexes = [self selectedRowIndexes];
4668 
4669  if ([selectedIndexes count] > 0)
4670  {
4671  var extend = (([theEvent modifierFlags] & CPShiftKeyMask) && _allowsMultipleSelection),
4672  i = [self selectedRow];
4673 
4674  if ([self _selectionIsBroken])
4675  {
4676  while ([selectedIndexes containsIndex:i])
4677  {
4678  shouldGoUpward ? i-- : i++;
4679  }
4680  _wasSelectionBroken = true;
4681  }
4682  else if (_wasSelectionBroken && ((shouldGoUpward && i !== [selectedIndexes firstIndex]) || (!shouldGoUpward && i !== [selectedIndexes lastIndex])))
4683  {
4684  shouldGoUpward ? i = [selectedIndexes firstIndex] - 1 : i = [selectedIndexes lastIndex];
4685  _wasSelectionBroken = false;
4686  }
4687  else
4688  {
4689  shouldGoUpward ? i-- : i++;
4690  }
4691  }
4692  else
4693  {
4694  var extend = NO;
4695  //no rows are currently selected
4696  if ([self numberOfRows] > 0)
4697  var i = shouldGoUpward ? [self numberOfRows] - 1 : 0; // if we select upward select the last row, otherwise select the first row
4698  }
4699 
4700  if (i >= [self numberOfRows] || i < 0)
4701  return;
4702 
4703 
4704  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldSelectRow_)
4705  {
4706 
4707  while (![_delegate tableView:self shouldSelectRow:i] && (i < [self numberOfRows] || i > 0))
4708  shouldGoUpward ? i-- : i++; //check to see if the row can be selected if it can't be then see if the next row can be selected
4709 
4710  // If the index still can be selected after the loop then just return.
4711  if (![_delegate tableView:self shouldSelectRow:i])
4712  return;
4713  }
4714 
4715  // If we go upward and see that this row is already selected we should deselect the row below.
4716  if (extend && [selectedIndexes containsIndex:i])
4717  {
4718  // The row we're on is the last to be selected.
4719  var differedLastSelectedRow = i;
4720 
4721  // no remove the one before/after it
4722  shouldGoUpward ? i++ : i--;
4723 
4724  [selectedIndexes removeIndex:i];
4725 
4726  //we're going to replace the selection
4727  extend = NO;
4728  }
4729  else if (extend)
4730  {
4731  if ([selectedIndexes containsIndex:i])
4732  {
4733  i = shouldGoUpward ? [selectedIndexes firstIndex] -1 : [selectedIndexes lastIndex] + 1;
4734  i = MIN(MAX(i, 0), [self numberOfRows] - 1);
4735  }
4736 
4737  [selectedIndexes addIndex:i];
4738  var differedLastSelectedRow = i;
4739  }
4740  else
4741  {
4742  selectedIndexes = [CPIndexSet indexSetWithIndex:i];
4743  var differedLastSelectedRow = i;
4744  }
4745 
4746  [self selectRowIndexes:selectedIndexes byExtendingSelection:extend];
4747 
4748  // we differ because selectRowIndexes: does its own thing which would set the wrong index
4749  _lastSelectedRow = differedLastSelectedRow;
4750 
4751  if (i !== CPNotFound)
4752  [self scrollRowToVisible:i];
4753 }
4754 
4755 @end
4756 
4757 @implementation CPTableView (Bindings)
4758 
4759 + (id)_binderClassForBinding:(CPString)aBinding
4760 {
4761  if (aBinding == @"content")
4762  return [CPTableContentBinder class];
4763 
4764  return [super _binderClassForBinding:aBinding];
4765 }
4766 
4770 - (CPString)_replacementKeyPathForBinding:(CPString)aBinding
4771 {
4772  if (aBinding === @"selectionIndexes")
4773  return @"selectedRowIndexes";
4774 
4775  return [super _replacementKeyPathForBinding:aBinding];
4776 }
4777 
4781 - (void)_establishBindingsIfUnbound:(id)destination
4782 {
4783  if ([[self infoForBinding:@"content"] objectForKey:CPObservedObjectKey] !== destination)
4784  {
4785  [super bind:@"content" toObject:destination withKeyPath:@"arrangedObjects" options:nil];
4786  _contentBindingExpicitelySet = NO;
4787  }
4788 
4789  // If the content binding was set manually assume the user is taking manual control of establishing bindings.
4790  if (!_contentBindingExpicitelySet)
4791  {
4792  if ([[self infoForBinding:@"selectionIndexes"] objectForKey:CPObservedObjectKey] !== destination)
4793  [self bind:@"selectionIndexes" toObject:destination withKeyPath:@"selectionIndexes" options:nil];
4794 
4795  if ([[self infoForBinding:@"sortDescriptors"] objectForKey:CPObservedObjectKey] !== destination)
4796  [self bind:@"sortDescriptors" toObject:destination withKeyPath:@"sortDescriptors" options:nil];
4797  }
4798 }
4799 
4800 - (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options
4801 {
4802  [super bind:aBinding toObject:anObject withKeyPath:aKeyPath options:options];
4803 
4804  if (aBinding == @"content")
4805  _contentBindingExpicitelySet = YES;
4806 }
4807 
4808 @end
4809 
4810 @implementation CPTableContentBinder : CPBinder
4811 {
4812  id _content;
4813 }
4814 
4815 - (void)setValueFor:(id)aBinding
4816 {
4817  var destination = [_info objectForKey:CPObservedObjectKey],
4818  keyPath = [_info objectForKey:CPObservedKeyPathKey];
4819 
4820  _content = [destination valueForKey:keyPath];
4821 
4822  [_source reloadData];
4823 }
4824 
4825 @end
4826 
4827 var CPTableViewDataSourceKey = @"CPTableViewDataSourceKey",
4828  CPTableViewDelegateKey = @"CPTableViewDelegateKey",
4829  CPTableViewHeaderViewKey = @"CPTableViewHeaderViewKey",
4830  CPTableViewTableColumnsKey = @"CPTableViewTableColumnsKey",
4831  CPTableViewRowHeightKey = @"CPTableViewRowHeightKey",
4832  CPTableViewIntercellSpacingKey = @"CPTableViewIntercellSpacingKey",
4833  CPTableViewSelectionHighlightStyleKey = @"CPTableViewSelectionHighlightStyleKey",
4834  CPTableViewMultipleSelectionKey = @"CPTableViewMultipleSelectionKey",
4835  CPTableViewEmptySelectionKey = @"CPTableViewEmptySelectionKey",
4836  CPTableViewColumnReorderingKey = @"CPTableViewColumnReorderingKey",
4837  CPTableViewColumnResizingKey = @"CPTableViewColumnResizingKey",
4838  CPTableViewColumnSelectionKey = @"CPTableViewColumnSelectionKey",
4839  CPTableViewColumnAutoresizingStyleKey = @"CPTableViewColumnAutoresizingStyleKey",
4840  CPTableViewGridColorKey = @"CPTableViewGridColorKey",
4841  CPTableViewGridStyleMaskKey = @"CPTableViewGridStyleMaskKey",
4842  CPTableViewUsesAlternatingBackgroundKey = @"CPTableViewUsesAlternatingBackgroundKey",
4843  CPTableViewAlternatingRowColorsKey = @"CPTableViewAlternatingRowColorsKey",
4844  CPTableViewHeaderViewKey = @"CPTableViewHeaderViewKey",
4845  CPTableViewCornerViewKey = @"CPTableViewCornerViewKey",
4846  CPTableViewAutosaveNameKey = @"CPTableViewAutosaveNameKey";
4847 
4848 @implementation CPTableView (CPCoding)
4849 
4850 - (id)initWithCoder:(CPCoder)aCoder
4851 {
4852  self = [super initWithCoder:aCoder];
4853 
4854  if (self)
4855  {
4856  //Configuring Behavior
4857  _allowsColumnReordering = [aCoder decodeBoolForKey:CPTableViewColumnReorderingKey];
4858  _allowsColumnResizing = [aCoder decodeBoolForKey:CPTableViewColumnResizingKey];
4859  _allowsMultipleSelection = [aCoder decodeBoolForKey:CPTableViewMultipleSelectionKey];
4860  _allowsEmptySelection = [aCoder decodeBoolForKey:CPTableViewEmptySelectionKey];
4861  _allowsColumnSelection = [aCoder decodeBoolForKey:CPTableViewColumnSelectionKey];
4862 
4863  //Setting Display Attributes
4864  _selectionHighlightStyle = [aCoder decodeIntForKey:CPTableViewSelectionHighlightStyleKey];
4865  _columnAutoResizingStyle = [aCoder decodeIntForKey:CPTableViewColumnAutoresizingStyleKey];
4866 
4867  _tableColumns = [aCoder decodeObjectForKey:CPTableViewTableColumnsKey] || [];
4868  [_tableColumns makeObjectsPerformSelector:@selector(setTableView:) withObject:self];
4869 
4870  _rowHeight = [aCoder decodeFloatForKey:CPTableViewRowHeightKey] || 23.0;
4871  _intercellSpacing = [aCoder decodeSizeForKey:CPTableViewIntercellSpacingKey];
4872 
4873  if (_CGSizeEqualToSize(_intercellSpacing, _CGSizeMakeZero()))
4874  _intercellSpacing = _CGSizeMake(3.0, 2.0);
4875 
4876  [self setGridColor:[aCoder decodeObjectForKey:CPTableViewGridColorKey]];
4877  _gridStyleMask = [aCoder decodeIntForKey:CPTableViewGridStyleMaskKey];
4878 
4879  _usesAlternatingRowBackgroundColors = [aCoder decodeObjectForKey:CPTableViewUsesAlternatingBackgroundKey];
4880  [self setAlternatingRowBackgroundColors:[aCoder decodeObjectForKey:CPTableViewAlternatingRowColorsKey]];
4881 
4882  _headerView = [aCoder decodeObjectForKey:CPTableViewHeaderViewKey];
4883  _cornerView = [aCoder decodeObjectForKey:CPTableViewCornerViewKey];
4884 
4885  [self setDataSource:[aCoder decodeObjectForKey:CPTableViewDataSourceKey]];
4886  [self setDelegate:[aCoder decodeObjectForKey:CPTableViewDelegateKey]];
4887 
4888  [self _init];
4889 
4890  [self viewWillMoveToSuperview:[self superview]];
4891 
4892  // Do this as late as possible to make sure the tableview is fully configured
4893  [self setAutosaveName:[aCoder decodeObjectForKey:CPTableViewAutosaveNameKey]];
4894  }
4895 
4896  return self;
4897 }
4898 
4899 - (void)encodeWithCoder:(CPCoder)aCoder
4900 {
4901  [super encodeWithCoder:aCoder];
4902 
4903  [aCoder encodeObject:_dataSource forKey:CPTableViewDataSourceKey];
4904  [aCoder encodeObject:_delegate forKey:CPTableViewDelegateKey];
4905 
4906  [aCoder encodeFloat:_rowHeight forKey:CPTableViewRowHeightKey];
4907  [aCoder encodeSize:_intercellSpacing forKey:CPTableViewIntercellSpacingKey];
4908 
4909  [aCoder encodeInt:_selectionHighlightStyle forKey:CPTableViewSelectionHighlightStyleKey];
4910  [aCoder encodeInt:_columnAutoResizingStyle forKey:CPTableViewColumnAutoresizingStyleKey];
4911 
4912  [aCoder encodeBool:_allowsMultipleSelection forKey:CPTableViewMultipleSelectionKey];
4913  [aCoder encodeBool:_allowsEmptySelection forKey:CPTableViewEmptySelectionKey];
4914  [aCoder encodeBool:_allowsColumnReordering forKey:CPTableViewColumnReorderingKey];
4915  [aCoder encodeBool:_allowsColumnResizing forKey:CPTableViewColumnResizingKey];
4916  [aCoder encodeBool:_allowsColumnSelection forKey:CPTableViewColumnSelectionKey];
4917 
4918  [aCoder encodeObject:_tableColumns forKey:CPTableViewTableColumnsKey];
4919 
4920  [aCoder encodeObject:[self gridColor] forKey:CPTableViewGridColorKey];
4921  [aCoder encodeInt:_gridStyleMask forKey:CPTableViewGridStyleMaskKey];
4922 
4923  [aCoder encodeBool:_usesAlternatingRowBackgroundColors forKey:CPTableViewUsesAlternatingBackgroundKey];
4924  [aCoder encodeObject:[self alternatingRowBackgroundColors] forKey:CPTableViewAlternatingRowColorsKey];
4925 
4926  [aCoder encodeObject:_cornerView forKey:CPTableViewCornerViewKey];
4927  [aCoder encodeObject:_headerView forKey:CPTableViewHeaderViewKey];
4928 
4929  [aCoder encodeObject:_autosaveName forKey:CPTableViewAutosaveNameKey];
4930 }
4931 
4932 @end
4933 
4934 @implementation CPIndexSet (tableview)
4935 
4936 - (void)removeMatches:(CPIndexSet)otherSet
4937 {
4938  var firstindex = [self firstIndex],
4939  index = MIN(firstindex, [otherSet firstIndex]),
4940  switchFlag = (index == firstindex);
4941 
4942  while (index != CPNotFound)
4943  {
4944  var indexSet = (switchFlag) ? otherSet : self,
4945  otherIndex = [indexSet indexGreaterThanOrEqualToIndex:index];
4946 
4947  if (otherIndex == index)
4948  {
4949  [self removeIndex:index];
4950  [otherSet removeIndex:index];
4951  }
4952 
4953  index = otherIndex;
4954  switchFlag = !switchFlag;
4955  }
4956 }
4957 
4958 @end
4959 
4960 @implementation _CPDropOperationDrawingView : CPView
4961 {
4962  unsigned dropOperation;
4966 }
4967 
4968 - (void)drawRect:(CGRect)aRect
4969 {
4970  if (tableView._destinationDragStyle === CPTableViewDraggingDestinationFeedbackStyleNone || isBlinking)
4971  return;
4972 
4973  var context = [[CPGraphicsContext currentContext] graphicsPort];
4974 
4975  CGContextSetStrokeColor(context, [CPColor colorWithHexString:@"4886ca"]);
4976  CGContextSetLineWidth(context, 3);
4977 
4978  if (currentRow === -1)
4979  {
4980  CGContextStrokeRect(context, [self bounds]);
4981  }
4982 
4983  else if (dropOperation === CPTableViewDropOn)
4984  {
4985  //if row is selected don't fill and stroke white
4986  var selectedRows = [tableView selectedRowIndexes],
4987  newRect = _CGRectMake(aRect.origin.x + 2, aRect.origin.y + 2, aRect.size.width - 4, aRect.size.height - 5);
4988 
4989  if ([selectedRows containsIndex:currentRow])
4990  {
4991  CGContextSetLineWidth(context, 2);
4992  CGContextSetStrokeColor(context, [CPColor whiteColor]);
4993  }
4994  else
4995  {
4996  CGContextSetFillColor(context, [CPColor colorWithRed:72 / 255 green:134 / 255 blue:202 / 255 alpha:0.25]);
4997  CGContextFillRoundedRectangleInRect(context, newRect, 8, YES, YES, YES, YES);
4998  }
4999  CGContextStrokeRoundedRectangleInRect(context, newRect, 8, YES, YES, YES, YES);
5000 
5001  }
5002  else if (dropOperation === CPTableViewDropAbove)
5003  {
5004  //reposition the view up a tad
5005  [self setFrameOrigin:_CGPointMake(_frame.origin.x, _frame.origin.y - 8)];
5006 
5007  var selectedRows = [tableView selectedRowIndexes];
5008 
5009  if ([selectedRows containsIndex:currentRow - 1] || [selectedRows containsIndex:currentRow])
5010  {
5011  CGContextSetStrokeColor(context, [CPColor whiteColor]);
5012  CGContextSetLineWidth(context, 4);
5013  //draw the circle thing
5014  CGContextStrokeEllipseInRect(context, _CGRectMake(aRect.origin.x + 4, aRect.origin.y + 4, 8, 8));
5015  //then draw the line
5016  CGContextBeginPath(context);
5017  CGContextMoveToPoint(context, 10, aRect.origin.y + 8);
5018  CGContextAddLineToPoint(context, aRect.size.width - aRect.origin.y - 8, aRect.origin.y + 8);
5019  CGContextClosePath(context);
5020  CGContextStrokePath(context);
5021 
5022  CGContextSetStrokeColor(context, [CPColor colorWithHexString:@"4886ca"]);
5023  CGContextSetLineWidth(context, 3);
5024  }
5025 
5026  //draw the circle thing
5027  CGContextStrokeEllipseInRect(context, _CGRectMake(aRect.origin.x + 4, aRect.origin.y + 4, 8, 8));
5028  //then draw the line
5029  CGContextBeginPath(context);
5030  CGContextMoveToPoint(context, 10, aRect.origin.y + 8);
5031  CGContextAddLineToPoint(context, aRect.size.width - aRect.origin.y - 8, aRect.origin.y + 8);
5032  CGContextClosePath(context);
5033  CGContextStrokePath(context);
5034  //CGContextStrokeLineSegments(context, [aRect.origin.x + 8, aRect.origin.y + 8, 300 , aRect.origin.y + 8]);
5035  }
5036 }
5037 
5038 - (void)blink
5039 {
5040  if (dropOperation !== CPTableViewDropOn)
5041  return;
5042 
5043  isBlinking = YES;
5044 
5045  var showCallback = function()
5046  {
5047  objj_msgSend(self, "setHidden:", NO)
5048  isBlinking = NO;
5049  };
5050 
5051  var hideCallback = function()
5052  {
5053  objj_msgSend(self, "setHidden:", YES)
5054  isBlinking = YES;
5055  };
5056 
5057  objj_msgSend(self, "setHidden:", YES);
5058  [CPTimer scheduledTimerWithTimeInterval:0.1 callback:showCallback repeats:NO];
5059  [CPTimer scheduledTimerWithTimeInterval:0.19 callback:hideCallback repeats:NO];
5060  [CPTimer scheduledTimerWithTimeInterval:0.27 callback:showCallback repeats:NO];
5061 }
5062 
5063 @end
5064 
5065 @implementation _CPColumnDragView : CPView
5066 {
5067  CPColor _lineColor;
5068 }
5069 
5070 - (id)initWithLineColor:(CPColor)aColor
5071 {
5072  self = [super initWithFrame:_CGRectMakeZero()];
5073 
5074  if (self)
5075  _lineColor = aColor;
5076 
5077  return self;
5078 }
5079 
5080 - (void)drawRect:(CGRect)aRect
5081 {
5082  var context = [[CPGraphicsContext currentContext] graphicsPort];
5083 
5084  CGContextSetStrokeColor(context, _lineColor);
5085 
5086  var points = [
5087  _CGPointMake(0.5, 0),
5088  _CGPointMake(0.5, aRect.size.height)
5089  ];
5090 
5091  CGContextStrokeLineSegments(context, points, 2);
5092 
5093  points = [
5094  _CGPointMake(aRect.size.width - 0.5, 0),
5095  _CGPointMake(aRect.size.width - 0.5, aRect.size.height)
5096  ];
5097 
5098  CGContextStrokeLineSegments(context, points, 2);
5099 }
5100 
5101 @end
5102 
5104 
5108 - (BOOL)disableAutomaticResizing
5109 {
5110  return _disableAutomaticResizing;
5111 }
5112 
5116 - (void)setDisableAutomaticResizing:(BOOL)aValue
5117 {
5118  _disableAutomaticResizing = aValue;
5119 }
5120 
5121 @end
5122 
5124 
5128 - (id)content
5129 {
5130  return _content;
5131 }
5132 
5136 - (void)setContent:(id)aValue
5137 {
5138  _content = aValue;
5139 }
5140 
5141 @end