API  0.9.8
 All Classes Files Functions Variables Typedefs 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 @global CPApp
27 
28 
29 CPTableViewColumnDidMoveNotification = @"CPTableViewColumnDidMoveNotification";
30 CPTableViewColumnDidResizeNotification = @"CPTableViewColumnDidResizeNotification";
31 CPTableViewSelectionDidChangeNotification = @"CPTableViewSelectionDidChangeNotification";
32 CPTableViewSelectionIsChangingNotification = @"CPTableViewSelectionIsChangingNotification";
33 
42 
69 
70 //CPTableViewDraggingDestinationFeedbackStyles
74 
75 //CPTableViewDropOperations
78 
79 CPSourceListGradient = @"CPSourceListGradient";
80 CPSourceListTopLineColor = @"CPSourceListTopLineColor";
81 CPSourceListBottomLineColor = @"CPSourceListBottomLineColor";
82 
83 // TODO: add docs
84 
88 
92 
94 CPTableViewUniformColumnAutoresizingStyle = 1; // FIX ME: This is FUBAR
99 
100 #define NUMBER_OF_COLUMNS() (_tableColumns.length)
101 #define UPDATE_COLUMN_RANGES_IF_NECESSARY() \
102  if (_dirtyTableColumnRangeIndex !== CPNotFound) \
103  [self _recalculateTableColumnRanges];
104 #define FULL_ROW_HEIGHT() (_rowHeight + _intercellSpacing.height)
105 #define ROW_BOTTOM(__heightInfo) (__heightInfo.y + __heightInfo.height + _intercellSpacing.height)
106 #define HAS_VARIABLE_ROW_HEIGHTS() (_implementedDelegateMethods & CPTableViewDelegate_tableView_heightOfRow_)
107 
108 
110 
111 @optional
112 - (BOOL)tableView:(CPTableView)aTableView acceptDrop:(id <CPDraggingInfo>)info row:(CPInteger)aRowIndex dropOperation:(CPTableViewDropOperation)operation;
113 - (BOOL)tableView:(CPTableView)aTableView writeRowsWithIndexes:(CPIndexSet)rowIndexes toPasteboard:(CPPasteboard)pboard;
114 - (CPArray)tableView:(CPTableView)aTableView namesOfPromisedFilesDroppedAtDestination:(CPURL)dropDestination forDraggedRowsWithIndexes:(CPIndexSet)anIndexSet;
115 - (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(id <CPDraggingInfo>)info proposedRow:(CPInteger)aRowIndex proposedDropOperation:(CPTableViewDropOperation)anOperation;
116 - (CPInteger)numberOfRowsInTableView:(CPTableView)aTableView;
117 - (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex;
118 - (void)tableView:(CPTableView)aTableView setObjectValue:(id)anObjectValue forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex;
119 - (void)tableView:(CPTableView)aTableView sortDescriptorsDidChange:(CPArray)oldDescriptors;
120 
121 @end
122 
124 
125 @optional
126 - (BOOL)selectionShouldChangeInTableView:(CPTableView)aTableView;
127 - (BOOL)tableView:(CPTableView)aTableView isGroupRow:(CPInteger)aRowIndex;
128 - (BOOL)tableView:(CPTableView)aTableView shouldEditTableColumn:(CPTableColumn)aTableView row:(CPInteger)aRowIndex;
129 - (BOOL)tableView:(CPTableView)aTableView shouldReorderColumn:(CPInteger)columnIndex toColumn:(NSInteger)newColumnIndex;
130 - (BOOL)tableView:(CPTableView)aTableView shouldSelectRow:(CPInteger)aRowIndex;
131 - (BOOL)tableView:(CPTableView)aTableView shouldSelectTableColumn:(CPTableColumn)aTableColumn;
132 - (BOOL)tableView:(CPTableView)aTableView shouldShowViewExpansionForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex;
133 - (BOOL)tableView:(CPTableView)aTableView shouldTrackView:(CPView)aView forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex;
134 - (BOOL)tableView:(CPTableView)aTableView shouldTypeSelectForEvent:(CPEvent)anEvent withCurrentSearchString:(CPString)searchString;
135 - (CPIndexSet)tableView:(CPTableView)aTableView selectionIndexesForProposedSelection:(CPIndexSet)proposedSelectionIndexes;
136 - (CPInteger)tableView:(CPTableView)aTableView nextTypeSelectMatchFromRow:(CPInteger)startRow toRow:(CPInteger)endRow forString:(CPString)searchString;
137 - (CPMenu)tableViewMenuForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex;
138 - (CPString)tableView:(CPTableView)aTableView toolTipForView:(CPView)aView rect:(CGRect)aRect tableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex mouseLocation:(CGPoint)mouseLocation;
139 - (CPString)tableView:(CPTableView)aTableView typeSelectStringForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex;
140 - (CPView)tableView:(CPTableView)aTableView dataViewForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex;
141 - (CPView)tableView:(CPTableView)aTableView viewForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex;
142 - (float)tableView:(CPTableView)aTableView heightOfRow:(CPInteger)aRowIndex;
143 - (void)tableView:(CPTableView)aTableView didClickTableColumn:(CPTableColumn)aTableColumn;
144 - (void)tableView:(CPTableView)aTableView didDragTableColumn:(CPTableColumn)aTableColumn;
145 - (void)tableView:(CPTableView)aTableView mouseDownInHeaderOfTableColumn:(CPTableColumn)aTableColumn;
146 - (void)tableView:(CPTableView)aTableView willDisplayView:(CPView)aView forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex;
147 - (void)tableView:(CPTableView)aTableView willRemoveView:(CPView)aView forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex;
148 - (void)tableViewSelectionDidChange:(CPNotification)aNotification;
149 - (void)tableViewSelectionIsChanging:(CPNotification)aNotification;
150 
151 @end
152 
153 @implementation _CPTableDrawView : CPView
154 {
155  id _tableView;
156 }
157 
158 - (id)initWithTableView:(CPTableView)aTableView
159 {
160  self = [super init];
161 
162  if (self)
163  _tableView = aTableView;
164 
165  return self;
166 }
167 
168 - (void)drawRect:(CGRect)aRect
169 {
170  var frame = [self frame],
172 
173  CGContextTranslateCTM(context, -CGRectGetMinX(frame), -CGRectGetMinY(frame));
174 
175  [_tableView _drawRect:aRect];
176 }
177 
178 @end
179 
202 @implementation CPTableView : CPControl
203 {
204  id <CPTableViewDataSource> _dataSource;
205  CPInteger _implementedDataSourceMethods;
206 
207  id <CPTableViewDelegate> _delegate;
208  CPInteger _implementedDelegateMethods;
209 
210  CPArray _tableColumns;
211  CPArray _tableColumnRanges;
212  CPInteger _dirtyTableColumnRangeIndex;
213  CPInteger _numberOfHiddenColumns;
214 
215  BOOL _reloadAllRows;
216  BOOL _invalidateObjectValuesCache;
217  Object _objectValues;
218 
219  CGRect _exposedRect;
220  CPIndexSet _exposedRows;
221  CPIndexSet _exposedColumns;
222 
223  Object _dataViewsForRows;
224  Object _cachedDataViews;
225  CPDictionary _archivedDataViews;
226  Object _unavailable_custom_cibs;
227 
228  //Configuring Behavior
229  BOOL _allowsColumnReordering;
230  BOOL _allowsColumnResizing;
231  BOOL _allowsColumnSelection;
232  BOOL _allowsMultipleSelection;
233  BOOL _allowsEmptySelection;
234 
235  CPArray _sortDescriptors;
236 
237  //Setting Display Attributes
238  CGSize _intercellSpacing;
239  float _rowHeight;
240 
241  BOOL _usesAlternatingRowBackgroundColors;
242  CPArray _alternatingRowBackgroundColors;
243 
244  unsigned _selectionHighlightStyle;
245  CPColor _unfocusedSelectionHighlightColor;
246  CPDictionary _unfocusedSourceListSelectionColor;
247  CPTableColumn _currentHighlightedTableColumn;
248  unsigned _gridStyleMask;
249 
250  unsigned _numberOfRows;
251  CPIndexSet _groupRows;
252 
253  CPArray _cachedRowHeights;
254 
255  // Persistence
256  CPString _autosaveName;
257  BOOL _autosaveTableColumns;
258 
259  CPTableHeaderView _headerView;
260  _CPCornerView _cornerView;
261 
262  CPIndexSet _selectedColumnIndexes;
263  CPIndexSet _selectedRowIndexes;
264  CPInteger _selectionAnchorRow;
265  CPInteger _lastSelectedRow;
266  CPIndexSet _previouslySelectedRowIndexes;
267  CGPoint _startTrackingPoint;
268  CPDate _startTrackingTimestamp;
269  BOOL _trackingPointMovedOutOfClickSlop;
270  CPInteger _editingRow;
271  CPInteger _editingColumn;
272 
273  _CPTableDrawView _tableDrawView;
274 
275  SEL _doubleAction;
276  CPInteger _clickedRow;
277  CPInteger _clickedColumn;
278  unsigned _columnAutoResizingStyle;
279 
280  int _lastTrackedRowIndex;
281  CGPoint _originalMouseDownPoint;
282  BOOL _verticalMotionCanDrag;
283  unsigned _destinationDragStyle;
284  BOOL _isSelectingSession;
285  CPIndexSet _draggedRowIndexes;
286  BOOL _wasSelectionBroken;
287 
288  _CPDropOperationDrawingView _dropOperationFeedbackView;
289  CPDragOperation _dragOperationDefaultMask;
290  int _retargetedDropRow;
291  CPDragOperation _retargetedDropOperation;
292 
293  BOOL _disableAutomaticResizing;
294  BOOL _lastColumnShouldSnap;
295  BOOL _implementsCustomDrawRow;
296  BOOL _isViewBased;
297  BOOL _contentBindingExplicitlySet;
298 
299  SEL _viewForTableColumnRowSelector;
300 
301  CPInteger _draggedColumnIndex;
302  BOOL _draggedColumnIsSelected;
303  BOOL _needsDifferedTableColumnRemove;
304  CPArray _differedColumnDataToRemove;
305 
306  Function _BlockDeselectView;
307  Function _BlockSelectView;
308 
309  CPView _observedClipView;
310 }
311 
315 + (CPString)defaultThemeClass
316 {
317  return @"tableview";
318 }
319 
323 + (CPDictionary)themeAttributes
324 {
325  return @{
326  @"alternating-row-colors": [CPNull null],
327  @"grid-color": [CPNull null],
328  @"grid-line-thickness": 1.0,
329  @"highlighted-grid-color": [CPNull null],
330  @"selection-color": [CPNull null],
331  @"sourcelist-selection-color": [CPNull null],
332  @"sort-image": [CPNull null],
333  @"sort-image-reversed": [CPNull null],
334  @"selection-radius": [CPNull null],
335  @"image-generic-file": [CPNull null],
336  @"default-row-height": 25.0,
337  @"dropview-on-background-color": [CPNull null],
338  @"dropview-on-border-color": [CPNull null],
339  @"dropview-on-border-width": [CPNull null],
340  @"dropview-on-border-radius": [CPNull null],
341  @"dropview-on-selected-background-color": [CPNull null],
342  @"dropview-on-selected-border-color": [CPNull null],
343  @"dropview-on-selected-border-width": [CPNull null],
344  @"dropview-on-selected-border-radius": [CPNull null],
345  @"dropview-above-border-color": [CPNull null],
346  @"dropview-above-border-width": [CPNull null],
347  @"dropview-above-selected-border-color": [CPNull null],
348  @"dropview-above-selected-border-width": [CPNull null]
349  };
350 }
351 
352 - (id)initWithFrame:(CGRect)aFrame
353 {
354  self = [super initWithFrame:aFrame];
355 
356  if (self)
357  {
358  //Configuring Behavior
359  _allowsColumnReordering = YES;
360  _allowsColumnResizing = YES;
361  _allowsMultipleSelection = NO;
362  _allowsEmptySelection = YES;
363  _allowsColumnSelection = NO;
364  _disableAutomaticResizing = NO;
365 
366  //Setting Display Attributes
367  _selectionHighlightStyle = CPTableViewSelectionHighlightStyleRegular;
368 
371  [[CPColor whiteColor], [CPColor colorWithRed:245.0 / 255.0 green:249.0 / 255.0 blue:252.0 / 255.0 alpha:1.0]]];
372 
373  _tableColumns = [];
374  _tableColumnRanges = [];
375  _dirtyTableColumnRangeIndex = CPNotFound;
376  _numberOfHiddenColumns = 0;
377 
378  _intercellSpacing = CGSizeMake(3.0, 2.0);
379  _rowHeight = [self valueForThemeAttribute:@"default-row-height"];
380 
381  [self setGridColor:[CPColor colorWithHexString:@"dce0e2"]];
382  [self setGridStyleMask:CPTableViewGridNone];
383 
384  [self setHeaderView:[[CPTableHeaderView alloc] initWithFrame:CGRectMake(0, 0, [self bounds].size.width, _rowHeight)]];
385  [self setCornerView:[[_CPCornerView alloc] initWithFrame:CGRectMake(0, 0, [CPScroller scrollerWidth], CGRectGetHeight([_headerView frame]))]];
386 
387  _currentHighlightedTableColumn = nil;
388 
389  _draggedRowIndexes = [CPIndexSet indexSet];
390  _verticalMotionCanDrag = YES;
391  _isSelectingSession = NO;
392  _retargetedDropRow = nil;
393  _retargetedDropOperation = nil;
394  _dragOperationDefaultMask = nil;
396  _contentBindingExplicitlySet = NO;
397 
399  [self _init];
400  }
401 
402  return self;
403 }
404 
405 
411 - (void)_init
412 {
413  _lastSelectedRow = _clickedColumn = _clickedRow = -1;
414 
415  _selectedColumnIndexes = [CPIndexSet indexSet];
416  _selectedRowIndexes = [CPIndexSet indexSet];
417 
418  _dropOperationFeedbackView = [[_CPDropOperationDrawingView alloc] initWithFrame:CGRectMakeZero()];
419  [_dropOperationFeedbackView setTableView:self];
420 
421  _lastColumnShouldSnap = NO;
422 
423  if (!_alternatingRowBackgroundColors)
424  _alternatingRowBackgroundColors = [[CPColor whiteColor], [CPColor colorWithHexString:@"e4e7ff"]];
425 
426  _tableColumnRanges = [];
427  _dirtyTableColumnRangeIndex = 0;
428  _numberOfHiddenColumns = 0;
429 
430  _objectValues = { };
431  _invalidateObjectValuesCache = NO;
432  _dataViewsForRows = { };
433  _numberOfRows = 0;
434  _exposedRows = [CPIndexSet indexSet];
435  _exposedColumns = [CPIndexSet indexSet];
436  _cachedDataViews = { };
437  _archivedDataViews = nil;
438  _viewForTableColumnRowSelector = nil;
439  _unavailable_custom_cibs = { };
440  _cachedRowHeights = [];
441 
442  _groupRows = [CPIndexSet indexSet];
443 
444  _tableDrawView = [[_CPTableDrawView alloc] initWithTableView:self];
445  [_tableDrawView setBackgroundColor:[CPColor clearColor]];
446  [self addSubview:_tableDrawView];
447 
448  _draggedColumnIndex = -1;
449  _draggedColumnIsSelected = NO;
450 
451  _editingRow = CPNotFound;
452  _editingColumn = CPNotFound;
453 
454 /* //gradients for the source list when CPTableView is NOT first responder or the window is NOT key
455  // FIX ME: we need to actually implement this.
456  _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);
457  _sourceListInactiveTopLineColor = [CPColor colorWithCalibratedRed:(173.0/255.0) green:(187.0/255.0) blue:(209.0/255.0) alpha:1.0];
458  _sourceListInactiveBottomLineColor = [CPColor colorWithCalibratedRed:(150.0/255.0) green:(161.0/255.0) blue:(183.0/255.0) alpha:1.0];*/
459  _differedColumnDataToRemove = [];
460  _needsDifferedTableColumnRemove = NO;
461  _implementsCustomDrawRow = [self implementsSelector:@selector(drawRow:clipRect:)];
462 
463  if (!_sortDescriptors)
464  _sortDescriptors = [];
465 
466  [self _initSubclass];
467 }
468 
469 - (void)_initSubclass
470 {
471  _BlockDeselectView = function(view, row, column)
472  {
473  [view unsetThemeState:CPThemeStateSelectedDataView];
474  };
475 
476  _BlockSelectView = function(view, row, column)
477  {
478  [view setThemeState:CPThemeStateSelectedDataView];
479  };
480 }
481 
547 - (void)setDataSource:(id <CPTableViewDataSource>)aDataSource
548 {
549  if (_dataSource === aDataSource)
550  return;
551 
552  _dataSource = aDataSource;
553  _implementedDataSourceMethods = 0;
554 
555  if (!_dataSource)
556  return;
557 
558  var hasContentBinding = !![self infoForBinding:@"content"];
559 
560  if ([_dataSource respondsToSelector:@selector(numberOfRowsInTableView:)])
561  _implementedDataSourceMethods |= CPTableViewDataSource_numberOfRowsInTableView_;
562 
563  if ([_dataSource respondsToSelector:@selector(tableView:objectValueForTableColumn:row:)])
565 
566  if ([_dataSource respondsToSelector:@selector(tableView:setObjectValue:forTableColumn:row:)])
568 
569  if ([_dataSource respondsToSelector:@selector(tableView:acceptDrop:row:dropOperation:)])
570  _implementedDataSourceMethods |= CPTableViewDataSource_tableView_acceptDrop_row_dropOperation_;
571 
572  if ([_dataSource respondsToSelector:@selector(tableView:namesOfPromisedFilesDroppedAtDestination:forDraggedRowsWithIndexes:)])
574 
575  if ([_dataSource respondsToSelector:@selector(tableView:validateDrop:proposedRow:proposedDropOperation:)])
577 
578  if ([_dataSource respondsToSelector:@selector(tableView:writeRowsWithIndexes:toPasteboard:)])
580 
581  if ([_dataSource respondsToSelector:@selector(tableView:sortDescriptorsDidChange:)])
582  _implementedDataSourceMethods |= CPTableViewDataSource_tableView_sortDescriptorsDidChange_;
583 
584  [self _reloadDataViews];
585 }
586 
590 - (id)dataSource
591 {
592  return _dataSource;
593 }
594 
595 //Loading Data
599 - (void)reloadData
600 {
601  [self _reloadDataViews];
602 }
603 
609 - (void)reloadDataForRowIndexes:(CPIndexSet)rowIndexes columnIndexes:(CPIndexSet)columnIndexes
610 {
611  [self reloadData];
612 }
613 
614 // Reloads the data, not the views
615 - (void)_reloadDataForRowIndexes:(CPIndexSet)rowIndexes columnIndexes:(CPIndexSet)columnIndexes
616 {
617  [self _enumerateViewsInRows:rowIndexes columns:columnIndexes usingBlock:function(view, row, column, stop)
618  {
619  var tableColumn = [_tableColumns objectAtIndex:column];
620  [self _setObjectValueForTableColumn:tableColumn row:row forView:view useCache:NO];
621  }];
622 }
623 
624 // Reloads the views AND the data
625 - (void)_reloadDataViews
626 {
627  //if (!_dataSource)
628  // return;
629 
630  _reloadAllRows = YES;
631  _objectValues = { };
632  _cachedRowHeights = [];
633 
634  // Otherwise, if we have a row marked as group with a
635  // index greater than the new number or rows
636  // it keeps the the graphical group style.
637  [_groupRows removeAllIndexes];
638 
639  // This updates the size too.
640  [self noteNumberOfRowsChanged];
641 
642  [self setNeedsLayout];
643  [self setNeedsDisplay:YES];
644 }
645 
646 //Target-action Behavior
653 - (void)setDoubleAction:(SEL)anAction
654 {
655  _doubleAction = anAction;
656 }
657 
661 - (SEL)doubleAction
662 {
663  return _doubleAction;
664 }
665 
666 /*
667  Returns the index of the the column the user clicked to trigger an action, or -1 if no column was clicked.
668 */
669 - (CPInteger)clickedColumn
670 {
671  return _clickedColumn;
672 }
673 
677 - (CPInteger)clickedRow
678 {
679  return _clickedRow;
680 }
681 
682 //Configuring Behavior
683 
687 - (void)setAllowsColumnReordering:(BOOL)shouldAllowColumnReordering
688 {
689  _allowsColumnReordering = !!shouldAllowColumnReordering;
690 }
691 
695 - (BOOL)allowsColumnReordering
696 {
697  return _allowsColumnReordering;
698 }
699 
704 - (void)setAllowsColumnResizing:(BOOL)shouldAllowColumnResizing
705 {
706  _allowsColumnResizing = !!shouldAllowColumnResizing;
707 }
708 
712 - (BOOL)allowsColumnResizing
713 {
714  return _allowsColumnResizing;
715 }
716 
721 - (void)setAllowsMultipleSelection:(BOOL)shouldAllowMultipleSelection
722 {
723  _allowsMultipleSelection = !!shouldAllowMultipleSelection;
724 }
725 
731 - (BOOL)allowsMultipleSelection
732 {
733  return _allowsMultipleSelection;
734 }
735 
740 - (void)setAllowsEmptySelection:(BOOL)shouldAllowEmptySelection
741 {
742  _allowsEmptySelection = !!shouldAllowEmptySelection;
743 }
744 
748 - (BOOL)allowsEmptySelection
749 {
750  return _allowsEmptySelection;
751 }
752 
758 - (void)setAllowsColumnSelection:(BOOL)shouldAllowColumnSelection
759 {
760  _allowsColumnSelection = !!shouldAllowColumnSelection;
761 }
762 
763 
767 - (BOOL)allowsColumnSelection
768 {
769  return _allowsColumnSelection;
770 }
771 
772 //Setting Display Attributes
779 - (void)setIntercellSpacing:(CGSize)aSize
780 {
781  if (CGSizeEqualToSize(_intercellSpacing, aSize))
782  return;
783 
784  _intercellSpacing = CGSizeMakeCopy(aSize);
785 
786  _dirtyTableColumnRangeIndex = 0; // so that _recalculateTableColumnRanges will work
787  [self _recalculateTableColumnRanges];
788 
789  [_headerView setNeedsDisplay:YES];
790  [_headerView setNeedsLayout];
791 
792  [self _reloadDataViews];
793 }
794 
798 - (CGSize)intercellSpacing
799 {
800  return CGSizeMakeCopy(_intercellSpacing);
801 }
802 
809 - (void)setRowHeight:(unsigned)aRowHeight
810 {
811  // Accept row heights such as "0".
812  aRowHeight = +aRowHeight;
813 
814  if (_rowHeight === aRowHeight)
815  return;
816 
817  _rowHeight = MAX(0.0, aRowHeight);
818 
819  [self setNeedsLayout];
820 }
821 
825 - (unsigned)rowHeight
826 {
827  return _rowHeight;
828 }
829 
835 - (void)setUsesAlternatingRowBackgroundColors:(BOOL)shouldUseAlternatingRowBackgroundColors
836 {
837  _usesAlternatingRowBackgroundColors = shouldUseAlternatingRowBackgroundColors;
838 }
839 
843 - (BOOL)usesAlternatingRowBackgroundColors
844 {
845  return _usesAlternatingRowBackgroundColors;
846 }
847 
853 - (void)setAlternatingRowBackgroundColors:(CPArray)alternatingRowBackgroundColors
854 {
855  [self setValue:alternatingRowBackgroundColors forThemeAttribute:@"alternating-row-colors"];
856 
857  [self setNeedsDisplay:YES];
858 }
859 
863 - (CPArray)alternatingRowBackgroundColors
864 {
865  return [self currentValueForThemeAttribute:@"alternating-row-colors"];
866 }
867 
879 - (unsigned)selectionHighlightStyle
880 {
881  return _selectionHighlightStyle;
882 }
883 
895 - (void)setSelectionHighlightStyle:(unsigned)aSelectionHighlightStyle
896 {
897  _selectionHighlightStyle = aSelectionHighlightStyle;
898 
899  if (aSelectionHighlightStyle === CPTableViewSelectionHighlightStyleSourceList)
901  else
903 
904  [self _updateHighlightWithOldRows:[CPIndexSet indexSet] newRows:_selectedRowIndexes];
905  [self _updateHighlightWithOldColumns:[CPIndexSet indexSet] newColumns:_selectedColumnIndexes];
906  [self setNeedsDisplay:YES];
907 }
908 
914 - (void)setSelectionHighlightColor:(CPColor)aColor
915 {
916  if ([[self selectionHighlightColor] isEqual:aColor])
917  return;
918 
919  [self setValue:aColor forThemeAttribute:@"selection-color"];
920  [self setNeedsDisplay:YES];
921 }
922 
926 - (CPColor)selectionHighlightColor
927 {
928  return [self currentValueForThemeAttribute:@"selection-color"];
929 }
930 
934 - (CPColor)unfocusedSelectionHighlightColor
935 {
936  if (!_unfocusedSelectionHighlightColor)
937  _unfocusedSelectionHighlightColor = [self _unfocusedSelectionColorFromColor:[self selectionHighlightColor] saturation:0];
938 
939  return _unfocusedSelectionHighlightColor;
940 }
941 
953 - (void)setSelectionGradientColors:(CPDictionary)aDictionary
954 {
955  [self setValue:aDictionary forThemeAttribute:@"sourcelist-selection-color"];
956  [self setNeedsDisplay:YES];
957 }
958 
967 - (CPDictionary)selectionGradientColors
968 {
969  return [self currentValueForThemeAttribute:@"sourcelist-selection-color"];
970 }
971 
981 - (CPColor)unfocusedSelectionGradientColors
982 {
983  if (!_unfocusedSourceListSelectionColor)
984  {
985  var sourceListColors = [self selectionGradientColors];
986 
987  _unfocusedSourceListSelectionColor = @{
988  CPSourceListGradient: [self _unfocusedGradientFromGradient:[sourceListColors objectForKey:CPSourceListGradient]],
989  CPSourceListTopLineColor: [self _unfocusedSelectionColorFromColor:[sourceListColors objectForKey:CPSourceListTopLineColor] saturation:0.2],
990  CPSourceListBottomLineColor: [self _unfocusedSelectionColorFromColor:[sourceListColors objectForKey:CPSourceListBottomLineColor] saturation:0.2]
991  };
992  }
993 
994  return _unfocusedSourceListSelectionColor;
995 }
996 
997 - (CPColor)_unfocusedSelectionColorFromColor:(CPColor)aColor saturation:(float)saturation
998 {
999  var hsb = [aColor hsbComponents];
1000 
1001  return [CPColor colorWithHue:hsb[0] saturation:hsb[1] * saturation brightness:hsb[2]];
1002 }
1003 
1004 - (CGGradient)_unfocusedGradientFromGradient:(CGGradient)aGradient
1005 {
1006  var colors = [aGradient.colors copy],
1007  count = [colors count];
1008 
1009  while (count--)
1010  {
1011  var rgba = colors[count].components,
1012  hsb = [self _unfocusedSelectionColorFromColor:[CPColor colorWithRed:rgba[0] green:rgba[1] blue:rgba[2] alpha:rgba[3]] saturation:0.2];
1013 
1014  colors[count] = CGColorCreate(aGradient.colorspace, [[hsb components] copy]);
1015  }
1016 
1017  return CGGradientCreateWithColors(aGradient.colorspace, colors, aGradient.locations);
1018 }
1019 
1024 - (void)setGridColor:(CPColor)aColor
1025 {
1026  [self setValue:aColor forThemeAttribute:@"grid-color"];
1027 
1028  [self setNeedsDisplay:YES];
1029 }
1030 
1034 - (CPColor)gridColor
1035 {
1036  return [self currentValueForThemeAttribute:@"grid-color"];;
1037 }
1038 
1044 - (void)setGridStyleMask:(unsigned)aGrideStyleMask
1045 {
1046  if (_gridStyleMask === aGrideStyleMask)
1047  return;
1048 
1049  _gridStyleMask = aGrideStyleMask;
1050 
1051  [self setNeedsDisplay:YES];
1052 }
1053 
1057 - (unsigned)gridStyleMask
1058 {
1059  return _gridStyleMask;
1060 }
1061 
1062 //Column Management
1063 
1068 - (void)addTableColumn:(CPTableColumn)aTableColumn
1069 {
1070  [_tableColumns addObject:aTableColumn];
1071  [aTableColumn setTableView:self];
1072 
1073  if (_dirtyTableColumnRangeIndex < 0)
1074  _dirtyTableColumnRangeIndex = NUMBER_OF_COLUMNS() - 1;
1075  else
1076  _dirtyTableColumnRangeIndex = MIN(NUMBER_OF_COLUMNS() - 1, _dirtyTableColumnRangeIndex);
1077 
1078  if ([[self sortDescriptors] count] > 0)
1079  {
1080  var mainSortDescriptor = [[self sortDescriptors] objectAtIndex:0];
1081 
1082  if (aTableColumn === [self _tableColumnForSortDescriptor:mainSortDescriptor])
1083  {
1084  var image = [mainSortDescriptor ascending] ? [self _tableHeaderSortImage] : [self _tableHeaderReverseSortImage];
1085  [self setIndicatorImage:image inTableColumn:aTableColumn];
1086  }
1087  }
1088 
1089  [self tile];
1090  [self setNeedsLayout];
1091 }
1092 
1097 - (void)removeTableColumn:(CPTableColumn)aTableColumn
1098 {
1099  if ([aTableColumn tableView] !== self)
1100  return;
1101 
1102  var index = [_tableColumns indexOfObjectIdenticalTo:aTableColumn];
1103 
1104  if (index === CPNotFound)
1105  return;
1106 
1107  // we defer the actual removal until the end of the runloop in order to keep a reference to the column.
1108  [_differedColumnDataToRemove addObject:{"column":aTableColumn, "shouldBeHidden": [aTableColumn isHidden]}];
1109  _needsDifferedTableColumnRemove = YES;
1110 
1111  [aTableColumn setHidden:YES];
1112  [aTableColumn setTableView:nil];
1113 
1114  var tableColumnUID = [aTableColumn UID];
1115 
1116  if (_objectValues[tableColumnUID])
1117  _objectValues[tableColumnUID] = nil;
1118 
1119  if (_dirtyTableColumnRangeIndex < 0)
1120  _dirtyTableColumnRangeIndex = index;
1121  else
1122  _dirtyTableColumnRangeIndex = MIN(index, _dirtyTableColumnRangeIndex);
1123 
1124  [self reloadData];
1125  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
1126 }
1127 
1132 - (void)_setDraggedColumn:(CPInteger)columnIndex
1133 {
1134  if (_draggedColumnIndex === columnIndex)
1135  return;
1136 
1137  // If ending a column drag, reselect the column if it was selected before the drag
1138  if (columnIndex === -1 && _draggedColumnIsSelected)
1139  [_selectedColumnIndexes addIndex:_draggedColumnIndex];
1140 
1141  _draggedColumnIndex = columnIndex;
1142 }
1143 
1144 /*
1145  @ignore
1146  Same as moveColumn:toColumn: but doesn't trigger an autosave
1147 */
1148 - (void)_moveColumn:(unsigned)fromIndex toColumn:(unsigned)toIndex
1149 {
1150  // Convert parameters such as "0" to 0.
1151  fromIndex = +fromIndex;
1152  toIndex = +toIndex;
1153 
1154  if (fromIndex === toIndex)
1155  return;
1156 
1157  if (_dirtyTableColumnRangeIndex < 0)
1158  _dirtyTableColumnRangeIndex = MIN(fromIndex, toIndex);
1159  else
1160  _dirtyTableColumnRangeIndex = MIN(fromIndex, toIndex, _dirtyTableColumnRangeIndex);
1161 
1162  var tableColumn = _tableColumns[fromIndex],
1163  selectedTableColumns = [_tableColumns objectsAtIndexes:_selectedColumnIndexes];
1164 
1165  [_tableColumns removeObjectAtIndex:fromIndex];
1166  [_tableColumns insertObject:tableColumn atIndex:toIndex];
1167 
1168  [[self headerView] setNeedsLayout];
1169  [[self headerView] setNeedsDisplay:YES];
1170 
1171  var range = CPMakeRange(MIN(fromIndex, toIndex), ABS(fromIndex - toIndex) + 1),
1172  layoutColumnIndexes = [CPIndexSet indexSetWithIndexesInRange:range],
1173  selectedColumnIndexes = [CPIndexSet indexSet];
1174 
1175  [_tableColumns enumerateObjectsUsingBlock:function(tableColumn, idx, stop)
1176  {
1177  if ([selectedTableColumns containsObjectIdenticalTo:tableColumn])
1178  [selectedColumnIndexes addIndex:idx];
1179  }];
1180 
1181  if ([_selectedColumnIndexes containsIndex:fromIndex])
1182  [selectedColumnIndexes addIndex:toIndex];
1183 
1184  if (_draggedColumnIndex !== -1)
1185  [layoutColumnIndexes removeIndex:toIndex];
1186 
1187  [self _layoutViewsForRowIndexes:_exposedRows columnIndexes:layoutColumnIndexes];
1188  [self selectColumnIndexes:selectedColumnIndexes byExtendingSelection:NO];
1189  [self setNeedsDisplay:YES];
1190 
1191  // Notify even if programmatically moving a column as in Cocoa.
1192  // TODO Only notify when a column drag operation ends, not each time a column reaches a new slot?
1193  [[CPNotificationCenter defaultCenter] postNotificationName:CPTableViewColumnDidMoveNotification
1194  object:self
1195  userInfo:@{ @"CPOldColumn": fromIndex, @"CPNewColumn": toIndex }];
1196 
1197  if (_implementedDelegateMethods & CPTableViewDelegate_tableViewColumnDidMove_)
1198  [_delegate tableViewColumnDidMove:[[CPNotification alloc] initWithName:CPTableViewColumnDidMoveNotification object:self userInfo:@{ @"CPOldColumn": fromIndex, @"CPNewColumn": toIndex }]];
1199 }
1200 
1206 - (void)moveColumn:(CPInteger)theColumnIndex toColumn:(CPInteger)theToIndex
1207 {
1208  [self _moveColumn:theColumnIndex toColumn:theToIndex];
1209  [self _autosave];
1210 }
1211 
1216 - (void)_tableColumnVisibilityDidChange:(CPTableColumn)aColumn
1217 {
1218  var columnIndex = [[self tableColumns] indexOfObjectIdenticalTo:aColumn];
1219 
1220  if (_dirtyTableColumnRangeIndex < 0)
1221  _dirtyTableColumnRangeIndex = columnIndex;
1222  else
1223  _dirtyTableColumnRangeIndex = MIN(columnIndex, _dirtyTableColumnRangeIndex);
1224 
1225  [[self headerView] setNeedsLayout];
1226  [[self headerView] setNeedsDisplay:YES];
1227 
1228  var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])];
1229  [self _layoutViewsForRowIndexes:rowIndexes columnIndexes:[CPIndexSet indexSetWithIndex:columnIndex]];
1230 }
1231 
1235 - (CPArray)tableColumns
1236 {
1237  return _tableColumns;
1238 }
1239 
1246 - (CPInteger)columnWithIdentifier:(CPString)anIdentifier
1247 {
1248  var index = 0,
1249  count = NUMBER_OF_COLUMNS();
1250 
1251  for (; index < count; ++index)
1252  if ([_tableColumns[index] identifier] === anIdentifier)
1253  return index;
1254 
1255  return CPNotFound;
1256 }
1257 
1264 - (CPTableColumn)tableColumnWithIdentifier:(CPString)anIdentifier
1265 {
1266  var index = [self columnWithIdentifier:anIdentifier];
1267 
1268  if (index === CPNotFound)
1269  return nil;
1270 
1271  return _tableColumns[index];
1272 }
1273 
1277 - (void)_didResizeTableColumn:(CPTableColumn)theColumn oldWidth:(int)oldWidth
1278 {
1279  [self _autosave];
1280 
1282  postNotificationName:CPTableViewColumnDidResizeNotification
1283  object:self
1284  userInfo:@{ @"CPTableColumn": theColumn, @"CPOldWidth": oldWidth }];
1285 
1286  if (_implementedDelegateMethods & CPTableViewDelegate_tableViewColumnDidResize_)
1287  [_delegate tableViewColumnDidResize:[[CPNotification alloc] initWithName:CPTableViewColumnDidResizeNotification object:self userInfo:@{ @"CPTableColumn": theColumn, @"CPOldWidth": oldWidth }]];
1288 }
1289 
1290 //Selecting Columns and Rows
1291 
1298 - (void)selectColumnIndexes:(CPIndexSet)columns byExtendingSelection:(BOOL)shouldExtendSelection
1299 {
1300  // If we're out of range, just return
1301  if (([columns firstIndex] != CPNotFound && [columns firstIndex] < 0) || [columns lastIndex] >= [self numberOfColumns] || (!shouldExtendSelection && [columns isEqualToIndexSet:_selectedColumnIndexes]) || (shouldExtendSelection && [columns count] === 0))
1302  return;
1303 
1304  // We deselect all rows when selecting columns.
1305  if ([_selectedRowIndexes count] > 0)
1306  {
1307  [self _updateHighlightWithOldRows:_selectedRowIndexes newRows:[CPIndexSet indexSet]];
1308  _selectedRowIndexes = [CPIndexSet indexSet];
1309  }
1310 
1311  var previousSelectedIndexes = [_selectedColumnIndexes copy];
1312 
1313  if (shouldExtendSelection)
1314  [_selectedColumnIndexes addIndexes:columns];
1315  else
1316  _selectedColumnIndexes = [columns copy];
1317 
1318  [self _updateHighlightWithOldColumns:previousSelectedIndexes newColumns:_selectedColumnIndexes];
1319  [self setNeedsDisplay:YES]; // FIXME: should be setNeedsDisplayInRect:enclosing rect of new (de)selected columns
1320  // but currently -drawRect: is not implemented here
1321  if (_headerView)
1322  [_headerView setNeedsDisplay:YES];
1323 
1324  [self _noteSelectionDidChange];
1325 }
1326 
1330 - (void)_setSelectedRowIndexes:(CPIndexSet)rows
1331 {
1332  if ([_selectedRowIndexes isEqualToIndexSet:rows])
1333  return;
1334 
1335  var previousSelectedIndexes = _selectedRowIndexes;
1336 
1337  _lastSelectedRow = ([rows count] > 0) ? [rows lastIndex] : -1;
1338  _selectedRowIndexes = [rows copy];
1339 
1340  [self _updateHighlightWithOldRows:previousSelectedIndexes newRows:_selectedRowIndexes];
1341  [self setNeedsDisplay:YES]; // FIXME: should be setNeedsDisplayInRect:enclosing rect of new (de)selected rows
1342  // but currently -drawRect: is not implemented here
1343 
1344  var binderClass = [[self class] _binderClassForBinding:@"selectionIndexes"];
1345  [[binderClass getBinding:@"selectionIndexes" forObject:self] reverseSetValueFor:@"selectedRowIndexes"];
1346 
1347  [self _noteSelectionDidChange];
1348 }
1349 
1356 - (void)selectRowIndexes:(CPIndexSet)rows byExtendingSelection:(BOOL)shouldExtendSelection
1357 {
1358  if ([rows isEqualToIndexSet:_selectedRowIndexes] ||
1359  (([rows firstIndex] != CPNotFound && [rows firstIndex] < 0) || [rows lastIndex] >= [self numberOfRows]) ||
1360  [self numberOfColumns] <= 0)
1361  return;
1362 
1363  // We deselect all columns when selecting rows.
1364  if ([_selectedColumnIndexes count] > 0)
1365  {
1366  [self _updateHighlightWithOldColumns:_selectedColumnIndexes newColumns:[CPIndexSet indexSet]];
1367  _selectedColumnIndexes = [CPIndexSet indexSet];
1368  if (_headerView)
1369  [_headerView setNeedsDisplay:YES];
1370  }
1371 
1372  var newSelectedIndexes;
1373  if (shouldExtendSelection)
1374  {
1375  newSelectedIndexes = [_selectedRowIndexes copy];
1376  [newSelectedIndexes addIndexes:rows];
1377  }
1378  else
1379  newSelectedIndexes = [rows copy];
1380 
1381  [self _setSelectedRowIndexes:newSelectedIndexes];
1382 }
1383 
1389 - (CPIndexSet)_cleanUpSelectionRowIndexes:(CPIndexSet)anIndexSet
1390 {
1391  if ([self _delegateRespondsToSelectionIndexesForProposedSelection])
1392  {
1393  return [self _sendDelegateSelectionIndexesForProposedSelection:anIndexSet];
1394  }
1395  else if ([self _delegateRespondsToShouldSelectRow])
1396  {
1397  var indexesToRemove = [CPIndexSet new],
1398  currentIndex = [anIndexSet firstIndex];
1399 
1400  while (currentIndex != CPNotFound)
1401  {
1402  if (![self _sendDelegateShouldSelectRow:currentIndex])
1403  [indexesToRemove addIndex:currentIndex];
1404 
1405  currentIndex = [anIndexSet indexGreaterThanIndex:currentIndex];
1406  }
1407 
1408  [anIndexSet removeIndexes:indexesToRemove];
1409 
1410  return anIndexSet;
1411  }
1412  else
1413  return anIndexSet;
1414 }
1415 
1419 - (void)_updateHighlightWithOldRows:(CPIndexSet)oldRows newRows:(CPIndexSet)newRows
1420 {
1421  [self _enumerateViewsInRows:oldRows columns:_exposedColumns usingBlock:_BlockDeselectView];
1422 
1423  if (_selectionHighlightStyle !== CPTableViewSelectionHighlightStyleNone)
1424  [self _enumerateViewsInRows:newRows columns:_exposedColumns usingBlock:_BlockSelectView];
1425 }
1426 
1430 - (void)_updateHighlightWithOldColumns:(CPIndexSet)oldColumns newColumns:(CPIndexSet)newColumns
1431 {
1432  var blockDeselectHeader = function(column, stop)
1433  {
1434  var headerView = [_tableColumns[column] headerView];
1435  [headerView unsetThemeState:CPThemeStateSelected];
1436  };
1437 
1438  var showSelection = _selectionHighlightStyle !== CPTableViewSelectionHighlightStyleNone;
1439 
1440  [self _enumerateViewsInRows:_exposedRows columns:oldColumns usingBlock:_BlockDeselectView];
1441  [oldColumns enumerateIndexesUsingBlock:blockDeselectHeader];
1442 
1443  if (_selectionHighlightStyle !== CPTableViewSelectionHighlightStyleNone)
1444  {
1445  [self _enumerateViewsInRows:_exposedRows columns:newColumns usingBlock:_BlockSelectView];
1446  [newColumns enumerateIndexesUsingBlock:function(column, stop)
1447  {
1448  var headerView = [_tableColumns[column] headerView];
1449  [headerView setThemeState:CPThemeStateSelected];
1450  }];
1451  }
1452 }
1453 
1457 - (int)selectedColumn
1458 {
1459  return [_selectedColumnIndexes lastIndex];
1460 }
1461 
1465 - (CPIndexSet)selectedColumnIndexes
1466 {
1467  return _selectedColumnIndexes;
1468 }
1469 
1473 - (int)selectedRow
1474 {
1475  return _lastSelectedRow;
1476 }
1477 
1481 - (CPIndexSet)selectedRowIndexes
1482 {
1483  return [_selectedRowIndexes copy];
1484 }
1485 
1491 - (void)deselectColumn:(CPInteger)anIndex
1492 {
1493  var selectedColumnIndexes = [_selectedColumnIndexes copy];
1495  [self selectColumnIndexes:selectedColumnIndexes byExtendingSelection:NO];
1496  [self _noteSelectionDidChange];
1497 }
1498 
1504 - (void)deselectRow:(CPInteger)aRow
1505 {
1506  var selectedRowIndexes = [_selectedRowIndexes copy];
1508  [self selectRowIndexes:selectedRowIndexes byExtendingSelection:NO];
1509  [self _noteSelectionDidChange];
1510 }
1511 
1515 - (CPInteger)numberOfSelectedColumns
1516 {
1517  return [_selectedColumnIndexes count];
1518 }
1519 
1523 - (CPInteger)numberOfSelectedRows
1524 {
1525  return [_selectedRowIndexes count];
1526 }
1527 
1534 - (BOOL)isColumnSelected:(CPInteger)anIndex
1535 {
1536  return [_selectedColumnIndexes containsIndex:anIndex];
1537 }
1538 
1545 - (BOOL)isRowSelected:(CPInteger)aRow
1546 {
1547  return [_selectedRowIndexes containsIndex:aRow];
1548 }
1549 
1550 
1555 - (void)deselectAll
1556 {
1559 }
1560 
1561 - (void)selectAll:(id)sender
1562 {
1563  if (_allowsMultipleSelection)
1564  {
1565  if (![self _sendDelegateSelectionShouldChangeInTableView])
1566  return;
1567 
1568  if ([[self selectedColumnIndexes] count])
1570  else
1571  {
1572  var range = [self _cleanUpSelectionRowIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])]];
1573  [self selectRowIndexes:range byExtendingSelection:NO];
1574  }
1575  }
1576 }
1577 
1578 - (void)deselectAll:(id)sender
1579 {
1580  if ([self allowsEmptySelection])
1581  {
1582  if (![self _sendDelegateSelectionShouldChangeInTableView])
1583  return;
1584 
1585  [self deselectAll];
1586  }
1587 }
1588 
1592 - (int)numberOfColumns
1593 {
1594  return NUMBER_OF_COLUMNS();
1595 }
1596 
1600 - (int)numberOfRows
1601 {
1602  return _numberOfRows;
1603 }
1604 
1605 - (int)_numberOfRows
1606 {
1607  var numberOfRows,
1608  contentBindingInfo = [self infoForBinding:@"content"];
1609 
1610  if (contentBindingInfo)
1611  {
1612  var destination = [contentBindingInfo objectForKey:CPObservedObjectKey],
1613  keyPath = [contentBindingInfo objectForKey:CPObservedKeyPathKey];
1614 
1615  numberOfRows = [[destination valueForKeyPath:keyPath] count];
1616  }
1617  else if (_dataSource && (_implementedDataSourceMethods & CPTableViewDataSource_numberOfRowsInTableView_))
1618  numberOfRows = [_dataSource numberOfRowsInTableView:self] || 0;
1619  else
1620  {
1621  if (_dataSource)
1622  CPLog(@"no content binding established and data source " + [_dataSource description] + " does not implement numberOfRowsInTableView:");
1623  numberOfRows = 0;
1624  }
1625 
1626  return numberOfRows;
1627 }
1628 
1629 
1633 - (CPView)cornerView
1634 {
1635  return _cornerView;
1636 }
1637 
1641 - (void)setCornerView:(CPView)aView
1642 {
1643  if (_cornerView === aView)
1644  return;
1645 
1646  _cornerView = aView;
1647 
1648  var scrollView = [self enclosingScrollView];
1649 
1650  if ([scrollView isKindOfClass:[CPScrollView class]] && [scrollView documentView] === self)
1651  [scrollView _updateCornerAndHeaderView];
1652 }
1653 
1657 - (CPView)headerView
1658 {
1659  return _headerView;
1660 }
1661 
1662 
1670 - (void)setHeaderView:(CPView)aHeaderView
1671 {
1672  if (_headerView === aHeaderView)
1673  return;
1674 
1675  [_headerView setTableView:nil];
1676 
1677  _headerView = aHeaderView;
1678 
1679  if (_headerView)
1680  {
1681  [_headerView setTableView:self];
1682  [_headerView setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), CGRectGetHeight([_headerView frame]))];
1683  }
1684  else
1685  {
1686  // If there is no header view, there should be no corner view
1687  [_cornerView removeFromSuperview];
1688  _cornerView = nil;
1689  }
1690 
1691  var scrollView = [self enclosingScrollView];
1692 
1693  if ([scrollView isKindOfClass:[CPScrollView class]] && [scrollView documentView] === self)
1694  [scrollView _updateCornerAndHeaderView];
1695 
1696  [self setNeedsLayout];
1697 }
1698 
1702 - (void)_recalculateTableColumnRanges
1703 {
1704  // Complexity:
1705  // O(Columns)
1706 
1707  if (_dirtyTableColumnRangeIndex < 0)
1708  return;
1709 
1710  _numberOfHiddenColumns = 0;
1711 
1712  var index = _dirtyTableColumnRangeIndex,
1713  count = NUMBER_OF_COLUMNS(),
1714  x = index === 0 ? 0.0 : CPMaxRange(_tableColumnRanges[index - 1]);
1715 
1716  for (; index < count; ++index)
1717  {
1718  var tableColumn = _tableColumns[index];
1719 
1720  if ([tableColumn isHidden])
1721  {
1722  _numberOfHiddenColumns += 1;
1723  _tableColumnRanges[index] = CPMakeRange(x, 0.0);
1724  }
1725  else
1726  {
1727  var width = [_tableColumns[index] width] + _intercellSpacing.width;
1728 
1729  _tableColumnRanges[index] = CPMakeRange(x, width);
1730 
1731  x += width;
1732  }
1733  }
1734 
1735  _tableColumnRanges.length = count;
1736  _dirtyTableColumnRangeIndex = CPNotFound;
1737 }
1738 
1745 - (CGRect)rectOfColumn:(CPInteger)aColumnIndex
1746 {
1747  // Complexity:
1748  // O(1)
1749 
1750  // Coerce aColumnIndex to a number in case it is a string.
1751  aColumnIndex = +aColumnIndex;
1752 
1753  if (aColumnIndex < 0 || aColumnIndex >= NUMBER_OF_COLUMNS())
1754  return CGRectMakeZero();
1755 
1756  if ([[_tableColumns objectAtIndex:aColumnIndex] isHidden])
1757  return CGRectMakeZero();
1758 
1760 
1761  var range = _tableColumnRanges[aColumnIndex];
1762 
1763  return CGRectMake(range.location, 0.0, range.length, CGRectGetHeight([self bounds]));
1764 }
1765 
1773 - (CGRect)_rectOfRow:(CPInteger)aRowIndex checkRange:(BOOL)checkRange
1774 {
1775  // Complexity:
1776  // O(1)
1777 
1778  var lastIndex = [self numberOfRows] - 1,
1779  validIndex = aRowIndex >= 0 && aRowIndex <= lastIndex;
1780 
1781  if (checkRange && !validIndex)
1782  return CGRectMakeZero();
1783 
1784  var y = 0,
1785  height,
1786  fixedHeightRows = 0;
1787 
1789  {
1790  [self _populateRowHeightCacheIfNeeded];
1791 
1792  // If the index is valid, we use the y and height of the given row.
1793  // If the index is invalid, we start from the bottom of the last row and use the default row height.
1794  var heightInfo;
1795 
1796  if (validIndex)
1797  {
1798  heightInfo = _cachedRowHeights[aRowIndex];
1799  y = heightInfo.y;
1800  height = heightInfo.height + _intercellSpacing.height;
1801  }
1802  else
1803  {
1804  height = FULL_ROW_HEIGHT();
1805 
1806  if (_numberOfRows > 0)
1807  {
1808  heightInfo = _cachedRowHeights[lastIndex];
1809  y = ROW_BOTTOM(heightInfo);
1810 
1811  // y is now at the top of the first row beyond the last valid row.
1812  // Add the height of any rows beyond that.
1813  fixedHeightRows = aRowIndex - _numberOfRows;
1814  }
1815  }
1816  }
1817  else
1818  {
1819  fixedHeightRows = aRowIndex;
1820  height = FULL_ROW_HEIGHT();
1821  }
1822 
1823  y += fixedHeightRows * FULL_ROW_HEIGHT();
1824 
1825  return CGRectMake(0.0, y, CGRectGetWidth([self bounds]), height);
1826 }
1827 
1833 - (CGRect)rectOfRow:(CPInteger)aRowIndex
1834 {
1835  return [self _rectOfRow:aRowIndex checkRange:YES];
1836 }
1837 
1843 - (CPRange)rowsInRect:(CGRect)aRect
1844 {
1845  // Complexity:
1846  // O(1)
1847 
1848  // If we have no rows, then we won't intersect anything.
1849  if (_numberOfRows <= 0)
1850  return CPMakeRange(0, 0);
1851 
1852  var bounds = [self bounds];
1853 
1854  // No rows if the rect doesn't even intersect us.
1855  if (!CGRectIntersectsRect(aRect, bounds))
1856  return CPMakeRange(0, 0);
1857 
1858  var firstRow = [self rowAtPoint:aRect.origin];
1859 
1860  // first row has to be undershot, because if not we wouldn't be intersecting.
1861  if (firstRow < 0)
1862  firstRow = 0;
1863 
1864  var lastRow = [self rowAtPoint:CGPointMake(0.0, CGRectGetMaxY(aRect))];
1865 
1866  // last row has to be overshot, because if not we wouldn't be intersecting.
1867  if (lastRow < 0)
1868  lastRow = _numberOfRows - 1;
1869 
1870  return CPMakeRange(firstRow, lastRow - firstRow + 1);
1871 }
1872 
1873 /*
1874  Return the range of rows that lie wholly or partially within aRect.
1875  If the bottom of the last real row is above the bottom of aRect,
1876  synthesized rows of the default height are added to fill aRect.
1877 */
1878 - (CPRange)_exposedRowsInRect:(CGRect)aRect
1879 {
1880  var rowRange = [self rowsInRect:aRect],
1881  lastRealRow = CPMaxRange(rowRange) - 1,
1882  rectOfLastRealRow = [self _rectOfRow:lastRealRow checkRange:NO],
1883  bottomOfRealRows = CGRectGetMaxY(rectOfLastRealRow),
1884  rectBottom = CGRectGetMaxY(aRect);
1885 
1886  // If the bottom of the last real row is at or below the bottom of aRect, we are done
1887  if (bottomOfRealRows >= rectBottom)
1888  return rowRange;
1889 
1890  var numberOfSynthesizedRows = CEIL((rectBottom - bottomOfRealRows) / FULL_ROW_HEIGHT());
1891 
1892  rowRange.length += numberOfSynthesizedRows;
1893 
1894  return rowRange;
1895 }
1896 
1902 - (CPIndexSet)columnIndexesInRect:(CGRect)aRect
1903 {
1904  // Complexity:
1905  // O(log numberOfColumns) if table view contains no hidden columns
1906  // O(numberOfColumns) if table view contains hidden columns
1907 
1908  var column = MAX(0, [self columnAtPoint:CGPointMake(aRect.origin.x, 0.0)]),
1909  lastColumn = [self columnAtPoint:CGPointMake(CGRectGetMaxX(aRect), 0.0)];
1910 
1911  if (lastColumn === CPNotFound)
1912  lastColumn = NUMBER_OF_COLUMNS() - 1;
1913 
1914  // Don't bother doing the expensive removal of hidden indexes if we have no hidden columns.
1915  if (_numberOfHiddenColumns <= 0)
1916  return [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(column, lastColumn - column + 1)];
1917 
1918  var indexSet = [CPIndexSet indexSet];
1919 
1920  for (; column <= lastColumn; ++column)
1921  {
1922  var tableColumn = _tableColumns[column];
1923 
1924  if (![tableColumn isHidden])
1925  [indexSet addIndex:column];
1926  }
1927 
1928  return indexSet;
1929 }
1930 
1936 - (CPInteger)columnAtPoint:(CGPoint)aPoint
1937 {
1938  // Complexity:
1939  // O(log numberOfColumns) if table view contains no hidden columns
1940  // O(numberOfColumns) if table view contains hidden columns
1941 
1942  var bounds = [self bounds];
1943 
1944  if (!CGRectContainsPoint(bounds, aPoint))
1945  return CPNotFound;
1946 
1948 
1949  var x = aPoint.x,
1950  low = 0,
1951  high = _tableColumnRanges.length - 1;
1952 
1953  while (low <= high)
1954  {
1955  var middle = FLOOR(low + (high - low) / 2),
1956  range = _tableColumnRanges[middle];
1957 
1958  if (x < range.location)
1959  high = middle - 1;
1960 
1961  else if (x >= CPMaxRange(range))
1962  low = middle + 1;
1963 
1964  else
1965  {
1966  var numberOfColumns = _tableColumnRanges.length;
1967 
1968  while (middle < numberOfColumns && [_tableColumns[middle] isHidden])
1969  ++middle;
1970 
1971  if (middle < numberOfColumns)
1972  return middle;
1973 
1974  return CPNotFound;
1975  }
1976  }
1977 
1978  return CPNotFound;
1979 }
1980 
1986 - (CPInteger)rowAtPoint:(CGPoint)aPoint
1987 {
1988  // Complexity:
1989  // O(1) for fixed height rows or point out of bounds
1990  // O(log numberOfRows) for variable height rows
1991 
1992  // aPoint.x must be within our bounds
1993  var bounds = [self bounds];
1994 
1995  if (aPoint.x < CGRectGetMinX(bounds) || aPoint.x >= CGRectGetMaxX(bounds))
1996  return -1;
1997 
1998  // aPoint.x is in bounds, now we just have to check aPoint.y
1999 
2001  {
2002  // First make sure aPoint.y is above the bottom of the last row, otherwise we might
2003  // search the (potentially large number of) rows for nothing.
2004  var heightInfo = [_cachedRowHeights lastObject];
2005 
2006  if (!heightInfo || aPoint.y >= ROW_BOTTOM(heightInfo))
2007  return -1;
2008 
2009  return [_cachedRowHeights indexOfObject:aPoint
2010  inSortedRange:nil
2011  options:0
2012  usingComparator:function(aPoint, heightInfo)
2013  {
2014  if (aPoint.y < heightInfo.y)
2015  return CPOrderedAscending;
2016 
2017  if (aPoint.y > ROW_BOTTOM(heightInfo))
2018  return CPOrderedDescending;
2019 
2020  return CPOrderedSame;
2021  }];
2022  }
2023  else
2024  {
2025  var row = FLOOR(aPoint.y / FULL_ROW_HEIGHT());
2026 
2027  return row >= _numberOfRows ? -1 : row;
2028  }
2029 }
2030 
2039 - (CPInteger)rowForView:(CPView)aView
2040 {
2041  var row;
2042 
2043  [self getColumn:nil row:@ref(row) forView:aView];
2044 
2045  return row;
2046 }
2047 
2056 - (CPInteger)columnForView:(CPView)aView
2057 {
2058  var column;
2059 
2060  [self getColumn:@ref(column) row:nil forView:aView];
2061 
2062  return column;
2063 }
2064 
2068 - (void)getColumn:(Function)columnRef row:(Function)rowRef forView:(CPView)aView
2069 {
2070  var columnResult = -1,
2071  rowResult = -1;
2072 
2073  if (aView && [aView isKindOfClass:[CPView class]] && ![aView isKindOfClass:[CPTableView class]])
2074  {
2075  var cellView = aView,
2076  contentView = [[self window] contentView],
2077  found = NO,
2078  max_rec = 100;
2079 
2080  while (max_rec--)
2081  {
2082  if (!cellView || cellView === contentView)
2083  {
2084  found = NO;
2085  break;
2086  }
2087  else
2088  {
2089  var superview = [cellView superview];
2090 
2091  if ([superview isKindOfClass:[CPTableView class]])
2092  {
2093  found = YES;
2094  break;
2095  }
2096 
2097  cellView = superview;
2098  }
2099  }
2100 
2101  if (found)
2102  {
2103  [self _enumerateViewsInRows:_exposedRows columns:_exposedColumns usingBlock:function(view, row, column, stop)
2104  {
2105 
2106  if (view === cellView)
2107  {
2108  columnResult = column;
2109  rowResult = row;
2110  stop(YES);
2111  }
2112  }];
2113  }
2114  }
2115 
2116  if (columnRef)
2117  columnRef(columnResult);
2118 
2119  if (rowRef)
2120  rowRef(rowResult);
2121 }
2122 
2130 - (CGRect)frameOfDataViewAtColumn:(CPInteger)aColumn row:(CPInteger)aRow
2131 {
2133 
2134  if (aColumn > [self numberOfColumns] || aRow > [self numberOfRows])
2135  return CGRectMakeZero();
2136 
2137  var tableColumnRange = _tableColumnRanges[aColumn],
2138  rectOfRow = [self rectOfRow:aRow],
2139  leftInset = FLOOR(_intercellSpacing.width / 2.0),
2140  topInset = FLOOR(_intercellSpacing.height / 2.0);
2141 
2142  return CGRectMake(tableColumnRange.location + leftInset, CGRectGetMinY(rectOfRow) + topInset, tableColumnRange.length - _intercellSpacing.width, CGRectGetHeight(rectOfRow) - _intercellSpacing.height);
2143 }
2144 
2148 - (void)resizeWithOldSuperviewSize:(CGSize)aSize
2149 {
2150  [super resizeWithOldSuperviewSize:aSize];
2151 
2152  if (_disableAutomaticResizing)
2153  return;
2154 
2155  var mask = _columnAutoResizingStyle;
2156 
2157  // should we actually do some resizing?
2158  if (!_lastColumnShouldSnap)
2159  {
2160  // did the clip view intersect the old tablesize?
2161  var superview = [self superview];
2162 
2163  if (!superview || ![superview isKindOfClass:[CPClipView class]])
2164  return;
2165 
2166  var superviewWidth = [superview bounds].size.width,
2167  lastColumnMaxX = CGRectGetMaxX([self rectOfColumn:[self numberOfColumns] -1]);
2168 
2169  // Fix me: this fires on the table setup at times
2170  if (lastColumnMaxX >= superviewWidth && lastColumnMaxX <= aSize.width || lastColumnMaxX <= superviewWidth && lastColumnMaxX >= aSize.width)
2171  _lastColumnShouldSnap = YES;
2173  return;
2174  }
2175 
2177  [self _resizeAllColumnUniformlyWithOldSize:aSize];
2179  [self sizeLastColumnToFit];
2181  [self _autoResizeFirstColumn];
2182 }
2183 
2187 - (void)_autoResizeFirstColumn
2188 {
2189  var superview = [self superview];
2190 
2191  if (!superview)
2192  return;
2193 
2195 
2196  var count = NUMBER_OF_COLUMNS(),
2197  columnToResize = nil,
2198  totalWidth = 0,
2199  i = 0;
2200 
2201  for (; i < count; i++)
2202  {
2203  var column = _tableColumns[i];
2204 
2205  if (![column isHidden])
2206  {
2207  if (!columnToResize)
2208  columnToResize = column;
2209  totalWidth += [column width] + _intercellSpacing.width;
2210  }
2211  }
2212 
2213  // If there is a visible column
2214  if (columnToResize)
2215  {
2216  var superviewSize = [superview bounds].size,
2217  newWidth = superviewSize.width - totalWidth;
2218 
2219  newWidth += [columnToResize width];
2220  [columnToResize _tryToResizeToWidth:newWidth];
2221  }
2222 
2223  [self setNeedsLayout];
2224 }
2225 
2226 
2231 - (void)_resizeAllColumnUniformlyWithOldSize:(CGSize)oldSize
2232 {
2233  // what we care about is the superview clip rect
2234  // FIX ME: if it's not in a scrollview this doesn't really work
2235  var superview = [self superview];
2236 
2237  if (!superview || ![superview isKindOfClass:[CPClipView class]])
2238  return;
2239 
2241 
2242  var superviewWidth = [superview bounds].size.width,
2243  count = NUMBER_OF_COLUMNS(),
2244  resizableColumns = [CPIndexSet indexSet],
2245  remainingSpace = 0.0,
2246  i = 0;
2247 
2248  // find resizable columns
2249  // FIX ME: we could cache resizableColumns after this loop and reuse it during the resize
2250  for (; i < count; i++)
2251  {
2252  var tableColumn = _tableColumns[i];
2253  if (![tableColumn isHidden] && ([tableColumn resizingMask] & CPTableColumnAutoresizingMask))
2254  [resizableColumns addIndex:i];
2255  }
2256 
2257  var maxXofColumns = CGRectGetMaxX([self rectOfColumn:[resizableColumns lastIndex]]),
2258  remainingSpace = superviewWidth - maxXofColumns,
2259  resizeableColumnsCount = [resizableColumns count],
2260  proportionate = 0;
2261 
2262  while (remainingSpace && resizeableColumnsCount)
2263  {
2264  // Divy out the space.
2265  proportionate += remainingSpace / resizeableColumnsCount;
2266 
2267  // Reset the remaining space to 0
2268  remainingSpace = 0.0;
2269 
2270  var index = CPNotFound;
2271 
2272  while ((index = [resizableColumns indexGreaterThanIndex:index]) !== CPNotFound)
2273  {
2274  var item = _tableColumns[index],
2275  proposedWidth = [item width] + proportionate,
2276  resizeLeftovers = [item _tryToResizeToWidth:proposedWidth];
2277 
2278  if (resizeLeftovers)
2279  {
2280  [resizableColumns removeIndex:index];
2281 
2282  remainingSpace += resizeLeftovers;
2283  }
2284  }
2285  }
2286 
2287  // now that we've reached the end we know there are likely rounding errors
2288  // so we should size the last resized to fit
2289 
2290  // find the last visisble column
2291  while (count-- && [_tableColumns[count] isHidden]);
2292 
2293  // find the max x, but subtract a single pixel since the spacing isn't applicable here.
2294  var delta = superviewWidth - CGRectGetMaxX([self rectOfColumn:count]) - ([self intercellSpacing].width || 1),
2295  newSize = [item width] + delta;
2296 
2297  [item _tryToResizeToWidth:newSize];
2298 }
2299 
2311 - (void)setColumnAutoresizingStyle:(unsigned)style
2312 {
2313  //FIX ME: CPTableViewSequentialColumnAutoresizingStyle and CPTableViewReverseSequentialColumnAutoresizingStyle are not yet implemented
2314  _columnAutoResizingStyle = style;
2315 }
2316 
2320 - (unsigned)columnAutoresizingStyle
2321 {
2322  return _columnAutoResizingStyle;
2323 }
2324 
2328 - (void)sizeLastColumnToFit
2329 {
2330  _lastColumnShouldSnap = YES;
2331 
2332  var superview = [self superview];
2333 
2334  if (!superview)
2335  return;
2336 
2337  var superviewSize = [superview bounds].size;
2338 
2340 
2341  var count = NUMBER_OF_COLUMNS();
2342 
2343  // Decrement the counter until we get to the last column that's not hidden
2344  while (count-- && [_tableColumns[count] isHidden]);
2345 
2346  // If the last column exists
2347  if (count >= 0)
2348  {
2349  var columnToResize = _tableColumns[count],
2350  newSize = MAX(0.0, superviewSize.width - CGRectGetMinX([self rectOfColumn:count]) - _intercellSpacing.width);
2351 
2352  [columnToResize _tryToResizeToWidth:newSize];
2353  }
2354 
2355  [self setNeedsLayout];
2356 }
2357 
2361 - (void)noteNumberOfRowsChanged
2362 {
2363  var oldNumberOfRows = _numberOfRows;
2364 
2365  _numberOfRows = [self _numberOfRows];
2366 
2367  _cachedRowHeights = [];
2368 
2369  // this line serves two purposes
2370  // 1. it updates the _numberOfRows cache with the -numberOfRows call
2371  // 2. it updates the row height cache if needed
2372  [self _noteHeightOfRowsWithIndexesChanged:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, _numberOfRows)]];
2373 
2374  // remove row indexes from the selection if they no longer exist
2375  var hangingSelections = oldNumberOfRows - _numberOfRows;
2376 
2377  if (hangingSelections > 0)
2378  {
2379  var previousSelectionCount = [_selectedRowIndexes count];
2380  [_selectedRowIndexes removeIndexesInRange:CPMakeRange(_numberOfRows, hangingSelections)];
2381 
2382  if (![_selectedRowIndexes containsIndex:[self selectedRow]])
2383  _lastSelectedRow = CPNotFound;
2384 
2385  // For optimal performance, only send a notification if indices were actually removed.
2386  if (previousSelectionCount > [_selectedRowIndexes count])
2387  [self _noteSelectionDidChange];
2388  }
2389 
2390  [self tile];
2391 }
2392 
2393 /*
2394  Populates the row height cache if necessary.
2395 */
2396 - (void)_populateRowHeightCacheIfNeeded
2397 {
2398  if ([self numberOfRows] !== _cachedRowHeights.length)
2399  [self noteHeightOfRowsWithIndexesChanged:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, _numberOfRows)]];
2400 }
2401 
2407 - (void)_noteHeightOfRowsWithIndexesChanged:(CPIndexSet)anIndexSet
2408 {
2409  if (!HAS_VARIABLE_ROW_HEIGHTS())
2410  return;
2411 
2412  // Update the height of the given rows by calling the delegate. Since the row height cache also contains
2413  // y coordinates, we have to update the y coordinates of all rows below the first valid row in the range.
2414  var i = [anIndexSet indexGreaterThanOrEqualToIndex:0];
2415 
2416  if (i === CPNotFound)
2417  return;
2418 
2419  var y = i < _cachedRowHeights.length ? _cachedRowHeights[i].y : 0;
2420 
2421  for (var count = [self numberOfRows]; i < count; ++i)
2422  {
2423  var height;
2424 
2425  if ([anIndexSet containsIndex:i])
2426  height = [self _sendDelegateHeightOfRow:i];
2427  else
2428  height = _cachedRowHeights[i].height || _rowHeight; // in case the cache entry is empty
2429 
2430  _cachedRowHeights[i] = {y:y, height:height};
2431  y += height + _intercellSpacing.height;
2432  }
2433 }
2434 
2440 - (void)noteHeightOfRowsWithIndexesChanged:(CPIndexSet)anIndexSet
2441 {
2442  [self _noteHeightOfRowsWithIndexesChanged:anIndexSet];
2443  [self _reloadDataViews];
2444 }
2445 
2449 - (void)tile
2450 {
2452 
2453  var width = _tableColumnRanges.length > 0 ? CPMaxRange([_tableColumnRanges lastObject]) : 0.0,
2454  superview = [self superview],
2455  height = 0;
2456 
2457  if (!HAS_VARIABLE_ROW_HEIGHTS())
2458  height = FULL_ROW_HEIGHT() * _numberOfRows;
2459  else if (_numberOfRows > 0)
2460  {
2461  [self _populateRowHeightCacheIfNeeded];
2462 
2463  var heightInfo = _cachedRowHeights[_cachedRowHeights.length - 1];
2464 
2465  height = ROW_BOTTOM(heightInfo);
2466  }
2467 
2468  if ([superview isKindOfClass:[CPClipView class]])
2469  {
2470  var superviewSize = [superview bounds].size;
2471 
2472  width = MAX(superviewSize.width, width);
2473  height = MAX(superviewSize.height, height);
2474  }
2475 
2476  [self setFrameSize:CGSizeMake(width, height)];
2477 
2478  [self setNeedsLayout];
2479  [self setNeedsDisplay:YES];
2480 }
2481 
2482 
2488 - (void)scrollRowToVisible:(int)rowIndex
2489 {
2490  var visible = [self visibleRect],
2491  rowRect = [self rectOfRow:rowIndex];
2492 
2493  visible.origin.y = rowRect.origin.y;
2494  visible.size.height = rowRect.size.height;
2495 
2496  [self scrollRectToVisible:visible];
2497 }
2498 
2504 - (void)scrollColumnToVisible:(int)columnIndex
2505 {
2506  var visible = [self visibleRect],
2507  colRect = [self rectOfColumn:columnIndex];
2508 
2509  visible.origin.x = colRect.origin.x;
2510  visible.size.width = colRect.size.width;
2511 
2512  [self scrollRectToVisible:visible];
2513  [_headerView scrollRectToVisible:colRect];
2514 }
2515 
2522 - (void)setAutosaveName:(CPString)theAutosaveName
2523 {
2524  if (_autosaveName === theAutosaveName)
2525  return;
2526 
2527  _autosaveName = theAutosaveName;
2528 
2529  [self setAutosaveTableColumns:!!theAutosaveName];
2530  [self _restoreFromAutosave];
2531 }
2532 
2536 - (CPString)autosaveName
2537 {
2538  return _autosaveName;
2539 }
2540 
2547 - (void)setAutosaveTableColumns:(BOOL)shouldAutosave
2548 {
2549  _autosaveTableColumns = shouldAutosave;
2550 }
2551 
2555 - (BOOL)autosaveTableColumns
2556 {
2557  return _autosaveTableColumns;
2558 }
2559 
2563 - (CPString)_columnsKeyForAutosaveName:(CPString)theAutosaveName
2564 {
2565  return @"CPTableView Columns " + theAutosaveName;
2566 }
2567 
2571 - (BOOL)_autosaveEnabled
2572 {
2573  return [self autosaveName] && [self autosaveTableColumns];
2574 }
2575 
2582 - (void)_autosave
2583 {
2584  if (![self _autosaveEnabled])
2585  return;
2586 
2587  var userDefaults = [CPUserDefaults standardUserDefaults],
2588  autosaveName = [self autosaveName];
2589 
2590  var columns = [self tableColumns],
2591  columnsSetup = [];
2592 
2593  for (var i = 0; i < [columns count]; i++)
2594  {
2595  var column = [columns objectAtIndex:i],
2596  metaData = @{
2597  @"identifier": [column identifier],
2598  @"width": [column width]
2599  };
2600 
2601  [columnsSetup addObject:metaData];
2602  }
2603 
2604  [userDefaults setObject:columnsSetup forKey:[self _columnsKeyForAutosaveName:autosaveName]];
2605 }
2606 
2610 - (void)_restoreFromAutosave
2611 {
2612  if (![self _autosaveEnabled])
2613  return;
2614 
2615  var userDefaults = [CPUserDefaults standardUserDefaults],
2616  autosaveName = [self autosaveName],
2617  tableColumns = [userDefaults objectForKey:[self _columnsKeyForAutosaveName:autosaveName]];
2618 
2619  if ([tableColumns count] != [[self tableColumns] count])
2620  return;
2621 
2622  for (var i = 0; i < [tableColumns count]; i++)
2623  {
2624  var metaData = [tableColumns objectAtIndex:i],
2625  columnIdentifier = [metaData objectForKey:@"identifier"],
2626  column = [self columnWithIdentifier:columnIdentifier],
2627  tableColumn = [self tableColumnWithIdentifier:columnIdentifier];
2628 
2629  if (tableColumn && column != CPNotFound)
2630  {
2631  [self _moveColumn:column toColumn:i];
2632  [tableColumn setWidth:[metaData objectForKey:@"width"]];
2633  }
2634  }
2635 }
2636 
2784 - (void)setDelegate:(id <CPTableViewDelegate>)aDelegate
2785 {
2786  if (_delegate === aDelegate)
2787  return;
2788 
2789  _delegate = aDelegate;
2790  _implementedDelegateMethods = 0;
2791 
2792  if ([_delegate respondsToSelector:@selector(selectionShouldChangeInTableView:)])
2793  _implementedDelegateMethods |= CPTableViewDelegate_selectionShouldChangeInTableView_;
2794 
2795  if ([_delegate respondsToSelector:@selector(tableView:viewForTableColumn:row:)])
2796  _implementedDelegateMethods |= CPTableViewDelegate_tableView_viewForTableColumn_row_;
2797  else if ([_delegate respondsToSelector:@selector(tableView:dataViewForTableColumn:row:)])
2798  {
2799  _implementedDelegateMethods |= CPTableViewDelegate_tableView_dataViewForTableColumn_row_;
2800  CPLog.warn("tableView:dataViewForTableColumn: is deprecated. You should use -tableView:viewForTableColumn: where you can request the view with -makeViewWithIdentifier:owner:");
2801  }
2802 
2803  [self _updateIsViewBased];
2804 
2805  if ([_delegate respondsToSelector:@selector(tableView:didClickTableColumn:)])
2806  _implementedDelegateMethods |= CPTableViewDelegate_tableView_didClickTableColumn_;
2807 
2808  if ([_delegate respondsToSelector:@selector(tableView:didDragTableColumn:)])
2809  _implementedDelegateMethods |= CPTableViewDelegate_tableView_didDragTableColumn_;
2810 
2811  if ([_delegate respondsToSelector:@selector(tableView:heightOfRow:)])
2812  _implementedDelegateMethods |= CPTableViewDelegate_tableView_heightOfRow_;
2813 
2814  if ([_delegate respondsToSelector:@selector(tableView:isGroupRow:)])
2815  _implementedDelegateMethods |= CPTableViewDelegate_tableView_isGroupRow_;
2816 
2817  if ([_delegate respondsToSelector:@selector(tableView:mouseDownInHeaderOfTableColumn:)])
2819 
2820  if ([_delegate respondsToSelector:@selector(tableView:nextTypeSelectMatchFromRow:toRow:forString:)])
2822 
2823  if ([_delegate respondsToSelector:@selector(tableView:selectionIndexesForProposedSelection:)])
2825 
2826  if ([_delegate respondsToSelector:@selector(tableView:shouldEditTableColumn:row:)])
2827  _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldEditTableColumn_row_;
2828 
2829  if ([_delegate respondsToSelector:@selector(tableView:shouldSelectRow:)])
2830  _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldSelectRow_;
2831 
2832  if ([_delegate respondsToSelector:@selector(tableView:shouldSelectTableColumn:)])
2833  _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldSelectTableColumn_;
2834 
2835  if ([_delegate respondsToSelector:@selector(tableView:shouldShowViewExpansionForTableColumn:row:)])
2837 
2838  if ([_delegate respondsToSelector:@selector(tableView:shouldTrackView:forTableColumn:row:)])
2840 
2841  if ([_delegate respondsToSelector:@selector(tableView:shouldTypeSelectForEvent:withCurrentSearchString:)])
2843 
2844  if ([_delegate respondsToSelector:@selector(tableView:toolTipForView:rect:tableColumn:row:mouseLocation:)])
2846 
2847  if ([_delegate respondsToSelector:@selector(tableView:typeSelectStringForTableColumn:row:)])
2849 
2850  if ([_delegate respondsToSelector:@selector(tableView:willDisplayView:forTableColumn:row:)])
2852 
2853  if ([_delegate respondsToSelector:@selector(tableView:willRemoveView:forTableColumn:row:)])
2855 
2856  if ([_delegate respondsToSelector:@selector(tableView:menuForTableColumn:row:)])
2857  _implementedDelegateMethods |= CPTableViewDelegate_tableViewMenuForTableColumn_row_;
2858 
2859  if ([_delegate respondsToSelector:@selector(tableView:shouldReorderColumn:toColumn:)])
2860  _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldReorderColumn_toColumn_;
2861 
2862  if ([_delegate respondsToSelector:@selector(tableViewColumnDidMove:)])
2863  _implementedDelegateMethods |= CPTableViewDelegate_tableViewColumnDidMove_;
2864 
2865  if ([_delegate respondsToSelector:@selector(tableViewColumnDidResize:)])
2866  _implementedDelegateMethods |= CPTableViewDelegate_tableViewColumnDidResize_;
2867 
2868  if ([_delegate respondsToSelector:@selector(tableViewSelectionDidChange:)])
2869  _implementedDelegateMethods |= CPTableViewDelegate_tableViewSelectionDidChange_;
2870 
2871  if ([_delegate respondsToSelector:@selector(tableViewSelectionIsChanging:)])
2872  _implementedDelegateMethods |= CPTableViewDelegate_tableViewSelectionIsChanging_;
2873 }
2874 
2879 {
2880  return _delegate;
2881 }
2882 
2886 - (void)_didClickTableColumn:(CPInteger)clickedColumn modifierFlags:(unsigned)modifierFlags
2887 {
2888  [self _changeSortDescriptorsForClickOnColumn:clickedColumn];
2889 
2890  if (_allowsColumnSelection)
2891  {
2892  if ([self _sendDelegateSelectionShouldChangeInTableView] && [self _sendDelegateShouldSelectTableColumn:clickedColumn])
2893  {
2894  [self _noteSelectionIsChanging];
2895  if (modifierFlags & CPPlatformActionKeyMask)
2896  {
2897  if ([self isColumnSelected:clickedColumn])
2898  [self deselectColumn:clickedColumn];
2899  else if ([self allowsMultipleSelection] == YES)
2900  [self selectColumnIndexes:[CPIndexSet indexSetWithIndex:clickedColumn] byExtendingSelection:YES];
2901 
2902  return;
2903  }
2904  else if (modifierFlags & CPShiftKeyMask)
2905  {
2906  // should be from clickedColumn to lastClickedColum with extending:(direction == previous selection)
2907  var startColumn = MIN(clickedColumn, [_selectedColumnIndexes lastIndex]),
2908  endColumn = MAX(clickedColumn, [_selectedColumnIndexes firstIndex]);
2909 
2910  [self selectColumnIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(startColumn, endColumn - startColumn + 1)]
2911  byExtendingSelection:YES];
2912 
2913  return;
2914  }
2915  else
2916  [self selectColumnIndexes:[CPIndexSet indexSetWithIndex:clickedColumn] byExtendingSelection:NO];
2917  }
2918  }
2919 
2920  [self _sendDelegateDidClickTableColumn:clickedColumn];
2921 }
2922 
2923 // From GNUSTEP
2928 - (void)_changeSortDescriptorsForClickOnColumn:(CPInteger)column
2929 {
2930  var tableColumn = [_tableColumns objectAtIndex:column],
2931  newMainSortDescriptor = [tableColumn sortDescriptorPrototype];
2932 
2933  if (!newMainSortDescriptor)
2934  return;
2935 
2936  var oldMainSortDescriptor = nil,
2937  oldSortDescriptors = [self sortDescriptors],
2938  newSortDescriptors = [CPArray arrayWithArray:oldSortDescriptors],
2939 
2940  e = [newSortDescriptors objectEnumerator],
2941  descriptor = nil,
2942  outdatedDescriptors = [CPArray array];
2943 
2944  if ([_sortDescriptors count] > 0)
2945  oldMainSortDescriptor = [[self sortDescriptors] objectAtIndex: 0];
2946 
2947  // Remove every main descriptor equivalents (normally only one)
2948  while ((descriptor = [e nextObject]) !== nil)
2949  {
2950  if ([[descriptor key] isEqual: [newMainSortDescriptor key]])
2951  [outdatedDescriptors addObject:descriptor];
2952  }
2953 
2954  // Invert the sort direction when the same column header is clicked twice
2955  if ([[newMainSortDescriptor key] isEqual:[oldMainSortDescriptor key]])
2956  newMainSortDescriptor = [oldMainSortDescriptor reversedSortDescriptor];
2957 
2958  [newSortDescriptors removeObjectsInArray:outdatedDescriptors];
2959  [newSortDescriptors insertObject:newMainSortDescriptor atIndex:0];
2960 
2961  [self setHighlightedTableColumn:tableColumn];
2962  [self setSortDescriptors:newSortDescriptors];
2963 }
2964 
2973 - (void)setIndicatorImage:(CPImage)anImage inTableColumn:(CPTableColumn)aTableColumn
2974 {
2975  if (aTableColumn)
2976  {
2977  var headerView = [aTableColumn headerView];
2978  if ([headerView respondsToSelector:@selector(_setIndicatorImage:)])
2979  [headerView _setIndicatorImage:anImage];
2980  }
2981 }
2982 
2986 - (CPImage)_tableHeaderSortImage
2987 {
2988  return [self currentValueForThemeAttribute:@"sort-image"];
2989 }
2990 
2994 - (CPImage)_tableHeaderReverseSortImage
2995 {
2996  return [self currentValueForThemeAttribute:@"sort-image-reversed"];
2997 }
2998 
3002 - (CPTableColumn)highlightedTableColumn
3003 {
3004  return _currentHighlightedTableColumn;
3005 }
3006 
3010 - (void)setHighlightedTableColumn:(CPTableColumn)aTableColumn
3011 {
3012  if (_currentHighlightedTableColumn == aTableColumn)
3013  return;
3014 
3015  if (_headerView)
3016  {
3017  if (_currentHighlightedTableColumn != nil)
3018  [[_currentHighlightedTableColumn headerView] unsetThemeState:CPThemeStateSelected];
3019 
3020  if (aTableColumn != nil)
3021  [[aTableColumn headerView] setThemeState:CPThemeStateSelected];
3022  }
3023 
3024  _currentHighlightedTableColumn = aTableColumn;
3025 }
3026 
3033 - (BOOL)canDragRowsWithIndexes:(CPIndexSet)rowIndexes atPoint:(CGPoint)mouseDownPoint
3034 {
3035  return [rowIndexes count] > 0 && [self numberOfRows] > 0 && [self numberOfColumns] > 0;
3036 }
3037 
3048 - (CPImage)dragImageForRowsWithIndexes:(CPIndexSet)dragRows tableColumns:(CPArray)theTableColumns event:(CPEvent)dragEvent offset:(CGPoint)dragImageOffset
3049 {
3050  return [self valueForThemeAttribute:@"image-generic-file"];
3051 }
3052 
3065 - (CPView)dragViewForRowsWithIndexes:(CPIndexSet)theDraggedRows tableColumns:(CPArray)theTableColumns event:(CPEvent)theDragEvent offset:(CGPoint)dragViewOffset
3066 {
3067  var bounds = [self bounds],
3068  dragView = [[CPView alloc] initWithFrame:bounds];
3069 
3070  [dragView setAlphaValue:0.7];
3071 
3072  // We have to fetch all the data views for the selected rows and columns
3073  // After that we can copy these add them to a transparent drag view and use that drag view
3074  // to make it appear we are dragging images of those rows (as you would do in regular Cocoa)
3075  [self _enumerateViewsInRows:theDraggedRows tableColumns:theTableColumns usingBlock:function(v, row, tableColumn, stop)
3076  {
3077  var column = [_tableColumns indexOfObjectIdenticalTo:tableColumn],
3078  newDataView = [self preparedViewAtColumn:column row:row];
3079 
3080  [dragView addSubview:newDataView];
3081  }];
3082 
3083  var dragPoint = [self convertPoint:[theDragEvent locationInWindow] fromView:nil];
3084  dragViewOffset.x = CGRectGetWidth(bounds) / 2 - dragPoint.x;
3085  dragViewOffset.y = CGRectGetHeight(bounds) / 2 - dragPoint.y;
3086 
3087  return dragView;
3088 }
3089 
3090 - (CPView)_dragViewForColumn:(CPInteger)columnIndex
3091 {
3092  var headerFrame = [_headerView frame],
3093  visibleRect = [self visibleRect],
3094  visibleRows = [self rowsInRect:visibleRect],
3095  columnRect = [self rectOfColumn:columnIndex],
3096  tableColumn = [[self tableColumns] objectAtIndex:columnIndex],
3097  tableColumnUID = [tableColumn UID],
3098  columnHeaderView = [tableColumn headerView],
3099  columnHeaderFrame = [columnHeaderView frame],
3100  frame = CGRectMake(MAX(CGRectGetMinX(columnRect) - CGRectGetMinX(visibleRect), 0.0),
3101  0.0,
3102  CGRectGetWidth(columnHeaderFrame),
3103  CGRectGetHeight(visibleRect) + CGRectGetHeight(headerFrame));
3104 
3105  // We need a wrapper view around the header and column, this is what will be dragged
3106  var dragView = [[_CPColumnDragDrawingView alloc] initWithFrame:frame];
3107 
3108  [dragView setTableView:self];
3109  [dragView setColumnIndex:columnIndex];
3110  [dragView setBackgroundColor:[CPColor clearColor]];
3111  [dragView setAlphaValue:0.6];
3112 
3113  // Now a view that clips the column data views, which itself is clipped to the content view
3114  var columnVisRect = CGRectIntersection(columnRect, visibleRect);
3115 
3116  frame = CGRectMake(0.0, CGRectGetHeight(headerFrame), CGRectGetWidth(columnVisRect), CGRectGetHeight(columnVisRect));
3117 
3118  var columnClipView = [[CPView alloc] initWithFrame:frame];
3119 
3120  [dragView addSubview:columnClipView];
3121  [dragView setColumnClipView:columnClipView];
3122  _draggedColumnIsSelected = [self isColumnSelected:columnIndex];
3123 
3124  var columnLeft = CGRectGetMinX(columnRect),
3125  offset = CGPointMake(columnLeft, CGRectGetMinY(visibleRect));
3126 
3127  [self _enumerateViewsInRows:_exposedRows columns:[CPIndexSet indexSetWithIndex:columnIndex] usingBlock:function(dataView, row, column, stop)
3128  {
3129  [self _addDraggedDataView:dataView toView:columnClipView forColumn:column row:row offset:offset];
3130 
3131  delete (_dataViewsForRows[row][tableColumnUID]);
3132  }];
3133 
3134  // Add the column header view
3135  columnHeaderFrame.origin = CGPointMakeZero();
3136 
3137  var dragColumnHeaderView = [[_CPTableColumnHeaderView alloc] initWithFrame:columnHeaderFrame],
3138  image = [columnHeaderView _indicatorImage];
3139 
3140  [dragColumnHeaderView setStringValue:[columnHeaderView stringValue]];
3141  [dragColumnHeaderView setThemeState:[columnHeaderView themeState]];
3142  [dragColumnHeaderView _setIndicatorImage:image];
3143 
3144  // Give it a tag so it can be found later
3145  [dragColumnHeaderView setTag:1];
3146 
3147  [dragView addSubview:dragColumnHeaderView];
3148 
3149  // While dragging, the column is deselected in the table view
3150  [_selectedColumnIndexes removeIndex:columnIndex];
3151 
3152  return dragView;
3153 }
3154 
3155 - (void)_addDraggedDataView:(CPView)aDataView toView:(CPView)aSuperview forColumn:(CPInteger)column row:(CPInteger)row offset:(CGPoint)offset
3156 {
3157  var dataViewFrame = [self frameOfDataViewAtColumn:column row:row];
3158 
3159  dataViewFrame.origin.x -= offset.x;
3160  // Offset by table header height - scroll position
3161  dataViewFrame.origin.y -= offset.y;
3162 
3163  [aDataView setFrame:dataViewFrame];
3164 
3165  [aSuperview addSubview:aDataView];
3166 }
3167 
3172 - (void)setDraggingSourceOperationMask:(CPDragOperation)mask forLocal:(BOOL)isLocal
3173 {
3174  //ignore local for the time being since only one capp app can run at a time...
3175  _dragOperationDefaultMask = mask;
3176 }
3177 
3183 - (void)setDropRow:(CPInteger)row dropOperation:(CPTableViewDropOperation)operation
3184 {
3185  if (row > [self numberOfRows] && operation === CPTableViewDropOn)
3186  {
3187  var numberOfRows = [self numberOfRows] + 1,
3188  reason = @"Attempt to set dropRow=" + row +
3189  " dropOperation=CPTableViewDropOn when [0 - " + numberOfRows + "] is valid range of rows.";
3190 
3191  [[CPException exceptionWithName:@"Error" reason:reason userInfo:nil] raise];
3192  }
3193 
3194  _retargetedDropRow = row;
3195  _retargetedDropOperation = operation;
3196 }
3197 
3209 - (void)setDraggingDestinationFeedbackStyle:(CPTableViewDraggingDestinationFeedbackStyle)aStyle
3210 {
3211  //FIX ME: this should vary up the highlight color, currently nothing is being done with it
3212  _destinationDragStyle = aStyle;
3213 }
3214 
3225 - (CPTableViewDraggingDestinationFeedbackStyle)draggingDestinationFeedbackStyle
3226 {
3227  return _destinationDragStyle;
3228 }
3229 
3235 - (void)setVerticalMotionCanBeginDrag:(BOOL)aFlag
3236 {
3237  _verticalMotionCanDrag = aFlag;
3238 }
3239 
3243 - (BOOL)verticalMotionCanBeginDrag
3244 {
3245  return _verticalMotionCanDrag;
3246 }
3247 
3248 - (CPTableColumn)_tableColumnForSortDescriptor:(CPSortDescriptor)theSortDescriptor
3249 {
3250  var tableColumns = [self tableColumns];
3251 
3252  for (var i = 0; i < [tableColumns count]; i++)
3253  {
3254  var tableColumn = [tableColumns objectAtIndex:i],
3255  sortDescriptorPrototype = [tableColumn sortDescriptorPrototype];
3256 
3257  if (!sortDescriptorPrototype)
3258  continue;
3259 
3260  if ([sortDescriptorPrototype key] === [theSortDescriptor key]
3261  && [sortDescriptorPrototype selector] === [theSortDescriptor selector])
3262  {
3263  return tableColumn;
3264  }
3265  }
3266 
3267  return nil;
3268 }
3269 
3275 - (void)setSortDescriptors:(CPArray)sortDescriptors
3276 {
3277  var oldSortDescriptors = [[self sortDescriptors] copy],
3278  newSortDescriptors = [CPArray array];
3279 
3280  if (sortDescriptors !== nil)
3281  [newSortDescriptors addObjectsFromArray:sortDescriptors];
3282 
3283  if ([newSortDescriptors isEqual:oldSortDescriptors])
3284  return;
3285 
3286  _sortDescriptors = newSortDescriptors;
3287 
3288  var oldColumn = nil,
3289  newColumn = nil;
3290 
3291  if ([newSortDescriptors count] > 0)
3292  {
3293  var newMainSortDescriptor = [newSortDescriptors objectAtIndex:0];
3294  newColumn = [self _tableColumnForSortDescriptor:newMainSortDescriptor];
3295  }
3296 
3297  if ([oldSortDescriptors count] > 0)
3298  {
3299  var oldMainSortDescriptor = [oldSortDescriptors objectAtIndex:0];
3300  oldColumn = [self _tableColumnForSortDescriptor:oldMainSortDescriptor];
3301  }
3302 
3303  var image = [newMainSortDescriptor ascending] ? [self _tableHeaderSortImage] : [self _tableHeaderReverseSortImage];
3304  [self setIndicatorImage:nil inTableColumn:oldColumn];
3305  [self setIndicatorImage:image inTableColumn:newColumn];
3306 
3307  [self _sendDataSourceSortDescriptorsDidChange:oldSortDescriptors];
3308 
3309  var binderClass = [[self class] _binderClassForBinding:@"sortDescriptors"];
3310  [[binderClass getBinding:@"sortDescriptors" forObject:self] reverseSetValueFor:@"sortDescriptors"];
3311 }
3312 
3316 - (CPArray)sortDescriptors
3317 {
3318  return _sortDescriptors;
3319 }
3320 
3324 - (id)_objectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex useCache:(BOOL)useCache
3325 {
3326  var objectValue;
3327 
3328  var tableColumnUID = [aTableColumn UID],
3329  tableColumnObjectValues = _objectValues[tableColumnUID];
3330 
3331  if (!tableColumnObjectValues)
3332  {
3333  tableColumnObjectValues = [];
3334  _objectValues[tableColumnUID] = tableColumnObjectValues;
3335  }
3336 
3337  if (useCache)
3338  objectValue = tableColumnObjectValues[aRowIndex];
3339 
3340  // tableView:objectValueForTableColumn:row: is optional if content bindings are in place.
3341  if (objectValue === undefined)
3342  {
3343  if ([self _dataSourceRespondsToObjectValueForTableColumn])
3344  {
3345  objectValue = [self _sendDataSourceObjectValueForTableColumn:aTableColumn row:aRowIndex];
3346  tableColumnObjectValues[aRowIndex] = objectValue;
3347  }
3348  else if (!_isViewBased && ![self infoForBinding:@"content"])
3349  {
3350  CPLog.warn(@"no content binding established and data source " + [_dataSource description] + " does not implement tableView:objectValueForTableColumn:row:");
3351  }
3352  }
3353 
3354  return objectValue;
3355 }
3356 
3357 
3361 - (CGRect)exposedRect
3362 {
3363  if (!_exposedRect)
3364  {
3365  var superview = [self superview];
3366 
3367  // FIXME: Should we be rect intersecting in case
3368  // there are multiple views in the clip view?
3369  if ([superview isKindOfClass:[CPClipView class]])
3370  _exposedRect = [superview bounds];
3371  else
3372  _exposedRect = [self bounds];
3373  }
3374 
3375  return _exposedRect;
3376 }
3377 
3381 - (void)load
3382 {
3383  if (_reloadAllRows)
3384  {
3385  [self _unloadDataViewsInRows:_exposedRows columns:_exposedColumns];
3386 
3387  _exposedRows = [CPIndexSet indexSet];
3388  _exposedColumns = [CPIndexSet indexSet];
3389 
3390  _reloadAllRows = NO;
3391  }
3392 
3393  var exposedRect = [self exposedRect],
3394  exposedRows = [CPIndexSet indexSetWithIndexesInRange:[self rowsInRect:exposedRect]],
3395  exposedColumns = [self columnIndexesInRect:exposedRect],
3396  obscuredRows = [_exposedRows copy],
3397  obscuredColumns = [_exposedColumns copy];
3398 
3399  [obscuredRows removeIndexes:exposedRows];
3400  [obscuredColumns removeIndexes:exposedColumns];
3401 
3402  var newlyExposedRows = [exposedRows copy],
3403  newlyExposedColumns = [exposedColumns copy];
3404 
3405  [newlyExposedRows removeIndexes:_exposedRows];
3406  [newlyExposedColumns removeIndexes:_exposedColumns];
3407 
3408  var previouslyExposedRows = [exposedRows copy],
3409  previouslyExposedColumns = [exposedColumns copy];
3410 
3411  [previouslyExposedRows removeIndexes:newlyExposedRows];
3412  [previouslyExposedColumns removeIndexes:newlyExposedColumns];
3413 
3414  [self _unloadDataViewsInRows:previouslyExposedRows columns:obscuredColumns];
3415  [self _unloadDataViewsInRows:obscuredRows columns:previouslyExposedColumns];
3416  [self _unloadDataViewsInRows:obscuredRows columns:obscuredColumns];
3417  [self _unloadDataViewsInRows:newlyExposedRows columns:newlyExposedColumns];
3418 
3419  [self _loadDataViewsInRows:previouslyExposedRows columns:newlyExposedColumns];
3420  [self _loadDataViewsInRows:newlyExposedRows columns:previouslyExposedColumns];
3421  [self _loadDataViewsInRows:newlyExposedRows columns:newlyExposedColumns];
3422 
3423  _exposedRows = exposedRows;
3424  _exposedColumns = exposedColumns;
3425 
3426  [_tableDrawView setFrame:exposedRect];
3427 
3428  [self setNeedsDisplay:YES];
3429 
3430  // if we have any columns to remove do that here
3431 
3432  if (_needsDifferedTableColumnRemove)
3433  {
3434  var removeCount = [_differedColumnDataToRemove count],
3435  removeIndexes = [CPIndexSet indexSet];
3436 
3437  for (var i = 0; i < removeCount; i++)
3438  {
3439  var data = _differedColumnDataToRemove[i],
3440  tableColumn = data.column,
3441  columnIdx = [_tableColumns indexOfObject:tableColumn];
3442 
3443  if (columnIdx !== CPNotFound)
3444  [removeIndexes addIndex:columnIdx];
3445  }
3446 
3447  var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])];
3448  [self _unloadDataViewsInRows:rowIndexes columns:removeIndexes];
3449 
3450  [_tableColumns removeObjectsAtIndexes:removeIndexes];
3451 
3452  _dirtyTableColumnRangeIndex = 0;
3453  [self _recalculateTableColumnRanges];
3454 
3455  [_differedColumnDataToRemove removeAllObjects];
3456  _needsDifferedTableColumnRemove = NO;
3457  }
3458 
3459  // Now clear all the leftovers
3460  // FIXME: this could be faster!
3461  for (var identifier in _cachedDataViews)
3462  {
3463  var dataViews = _cachedDataViews[identifier],
3464  count = dataViews.length;
3465 
3466  while (count--)
3467  [dataViews[count] removeFromSuperview];
3468  }
3469 }
3470 
3474 - (void)_unloadDataViewsInRows:(CPIndexSet)rowIndexes columns:(CPIndexSet)columnIndexes
3475 {
3476  if (![rowIndexes count] || ![columnIndexes count] || [columnIndexes lastIndex] >= [_tableColumns count])
3477  return;
3478 
3479  // If and edited view is about to be unloaded, cleanup its state before enqueuing.
3480  if ([columnIndexes containsIndex:_editingColumn] && [rowIndexes containsIndex:_editingRow])
3481  [self _resignEditedView];
3482 
3483  var tableColumns = [_tableColumns objectsAtIndexes:columnIndexes];
3484 
3485  [self _enumerateViewsInRows:rowIndexes tableColumns:tableColumns usingBlock:function(dataView, row, tableColumn, stop)
3486  {
3487  var dataViewsForRows = _dataViewsForRows[row],
3488  tableColumnUID = [tableColumn UID];
3489 
3490  delete (dataViewsForRows[tableColumnUID]);
3491 
3492  [self _sendDelegateWillRemoveView:dataView forTableColumn:tableColumn row:row];
3493  [self _enqueueReusableDataView:dataView];
3494  }];
3495 }
3496 
3500 - (void)_loadDataViewsInRows:(CPIndexSet)rowIndexes columns:(CPIndexSet)columnIndexes
3501 {
3502  if (![rowIndexes count] || ![columnIndexes count])
3503  return;
3504 
3506 
3507  if (_numberOfHiddenColumns > 0)
3508  columnIndexes = [columnIndexes indexesPassingTest:function(idx, stop)
3509  {
3510  return ![_tableColumns[idx] isHidden];
3511  }];
3512 
3513  [rowIndexes enumerateIndexesUsingBlock:function(rowIndex, stopRow)
3514  {
3515  if (!_dataViewsForRows[rowIndex])
3516  _dataViewsForRows[rowIndex] = {};
3517 
3518  var dataViewsForRow = _dataViewsForRows[rowIndex],
3519  isRowSelected = [self isRowSelected:rowIndex],
3520  row = rowIndex;
3521 
3522  [columnIndexes enumerateIndexesUsingBlock:function(columnIndex, stopCol)
3523  {
3524  var tableColumn = _tableColumns[columnIndex],
3525  tableColumnUID = [tableColumn UID],
3526  dataView = [self _preparedViewAtColumn:columnIndex row:row isRowSelected:isRowSelected];
3527 
3528  if ([dataView superview] !== self)
3529  [self addSubview:dataView];
3530 
3531  dataViewsForRow[tableColumnUID] = dataView;
3532  }];
3533  }];
3534 }
3535 
3536 - (CPView)preparedViewAtColumn:(CPInteger)column row:(CPInteger)row
3537 {
3538  return [self _preparedViewAtColumn:column row:row isRowSelected:[self isRowSelected:row]];
3539 }
3540 
3541 - (CPView)_preparedViewAtColumn:(CPInteger)column row:(CPInteger)row isRowSelected:(BOOL)isRowSelected
3542 {
3543  var tableColumn = _tableColumns[column],
3544  tableColumnUID = [tableColumn UID],
3545  dataView = [self _newDataViewForRow:row tableColumn:tableColumn];
3546 
3547  [dataView setFrame:[self frameOfDataViewAtColumn:column row:row]];
3548 
3549  [self _setObjectValueForTableColumn:tableColumn row:row forView:dataView];
3550 
3551  if (_selectionHighlightStyle !== CPTableViewSelectionHighlightStyleNone && (isRowSelected || [self isColumnSelected:column]))
3552  _BlockSelectView(dataView, row, column);
3553  else
3554  _BlockDeselectView(dataView, row, column);
3555 
3556  // FIX ME: for performance reasons we might consider diverging from cocoa and moving this to the reloadData method
3557  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_isGroupRow_)
3558  {
3559  if ([_delegate tableView:self isGroupRow:row])
3560  {
3561  [_groupRows addIndex:row];
3562  [dataView setThemeState:CPThemeStateGroupRow];
3563  }
3564  else
3565  {
3566  [_groupRows removeIndexesInRange:CPMakeRange(row, 1)];
3567  [dataView unsetThemeState:CPThemeStateGroupRow];
3568  }
3569 
3570  [self setNeedsDisplay:YES];
3571  }
3572 
3573  [self _sendDelegateWillDisplayView:dataView forTableColumn:tableColumn row:row];
3574 
3575  return dataView;
3576 }
3577 
3578 - (void)_setObjectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow forView:(CPView)aDataView
3579 {
3580  [self _setObjectValueForTableColumn:aTableColumn row:aRow forView:aDataView useCache:!_invalidateObjectValuesCache];
3581 }
3582 
3583 - (void)_setObjectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow forView:(CPView)aDataView useCache:(BOOL)useCache
3584 {
3585  if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_objectValueForTableColumn_row_)
3586  [aDataView setObjectValue:[self _objectValueForTableColumn:aTableColumn row:aRow useCache:useCache]];
3587 
3588  // This gives the table column an opportunity to apply its bindings.
3589  // It will override the value set above if there is a binding.
3590 
3591  if (_contentBindingExplicitlySet)
3592  [self _prepareContentBindedDataView:aDataView forRow:aRow];
3593  else
3594  // For both cell-based and view-based
3595  [aTableColumn _prepareDataView:aDataView forRow:aRow];
3596 }
3597 
3598 - (void)_prepareContentBindedDataView:(CPView)dataView forRow:(CPInteger)aRow
3599 {
3600  var binder = [CPTableContentBinder getBinding:@"content" forObject:self],
3601  content = [binder content],
3602  rowContent = [content objectAtIndex:aRow];
3603 
3604  [dataView setObjectValue:rowContent];
3605 }
3606 
3610 - (void)_layoutViewsForRowIndexes:(CPIndexSet)rowIndexes columnIndexes:(CPIndexSet)columnIndexes
3611 {
3612  [self _enumerateViewsInRows:rowIndexes columns:columnIndexes usingBlock:function(view, row, column, stop)
3613  {
3614  [view setFrame:[self frameOfDataViewAtColumn:column row:row]];
3615  }];
3616 }
3617 
3621 - (CPView)_newDataViewForRow:(CPInteger)aRow tableColumn:(CPTableColumn)aTableColumn
3622 {
3623  var view = nil;
3624 
3625  if (_viewForTableColumnRowSelector)
3626  view = objj_msgSend(self, _viewForTableColumnRowSelector, aTableColumn, aRow);
3627 
3628  if (!view)
3629  {
3630  // For cell-based tables, use the dataView prototype identifier as view identifier
3631  // instead of column identifier because 1/ the column identifier may be nil
3632  // and 2/ the tableColumn dataView may change after the column identifier was set.
3633  var identifier;
3634 
3635  if (_isViewBased)
3636  {
3637  identifier = [aTableColumn identifier];
3638  view = [self makeViewWithIdentifier:identifier owner:_delegate];
3639  }
3640  else
3641  {
3642  identifier = [[aTableColumn dataView] UID];
3643  view = [self makeViewWithIdentifier:identifier owner:_delegate];
3644 
3645  if (!view)
3646  view = [aTableColumn _newDataView];
3647  }
3648 
3649  [view setIdentifier:identifier];
3650  }
3651 
3652  return view;
3653 }
3654 
3655 /*
3656  Returns a view with the specified identifier.
3657 
3658  @param identifier The view identifier. Must not be nil.
3659  @param owner The owner of the CIB that may be loaded and instantiated to create a new view with the particular identifier.
3660  @return A view for the row.
3661 
3662  @discussion
3663  Typically identifier is associated with an external CIB and the table view will automatically instantiate the CIB with the provided owner. The owner of the CIB that may be loaded and instantiated to create a new view with the particular identifier is typically the table view’s delegate. The owner is useful in setting up outlets and target and actions from the view.
3664 
3665  This method will typically be called by the delegate in tableView:viewForTableColumn:row:, but it can also be overridden to provide custom views for the identifier. This method may also return a reused view with the same identifier that was no longer available on screen.
3666 */
3667 - (id)makeViewWithIdentifier:(CPString)anIdentifier owner:(id)anOwner
3668 {
3669  if (!anIdentifier)
3670  return nil;
3671 
3672  var view = nil,
3673  // See if we have some reusable views available
3674  reusableViews = _cachedDataViews[anIdentifier];
3675 
3676  if (reusableViews && reusableViews.length)
3677  view = reusableViews.pop();
3678  // Otherwise see if we have a view in the cib with this identifier
3679  else if (_isViewBased)
3680  view = [self _unarchiveViewWithIdentifier:anIdentifier owner:anOwner];
3681 
3682  return view;
3683 }
3684 
3697 - (id)viewAtColumn:(CPInteger)column row:(CPInteger)row makeIfNecessary:(BOOL)makeIfNecessary
3698 {
3699  if (row > (_numberOfRows - 1))
3700  [CPException raise:CPInvalidArgumentException
3701  reason:@"Row " + row + " out of row range [0-" + (_numberOfRows - 1) + "] for rowViewAtRow:createIfNeeded:"];
3702 
3703  if (column > (NUMBER_OF_COLUMNS() - 1))
3704  [CPException raise:CPInvalidArgumentException
3705  reason:@"Column " + column + " out of row range [0-" + (NUMBER_OF_COLUMNS ()- 1) + "] for rowViewAtRow:createIfNeeded:"];
3706 
3707  var dataViewsForRow = _dataViewsForRows[row],
3708  tableColumn = _tableColumns[column],
3709  tableColumnUID = [tableColumn UID],
3710  view = dataViewsForRow ? dataViewsForRow[tableColumnUID] : nil;
3711 
3712  if (!makeIfNecessary)
3713  return view || nil;
3714 
3715  if (!view)
3716  {
3717  if (!dataViewsForRow)
3718  {
3719  dataViewsForRow = {}
3720  _dataViewsForRows[row] = dataViewsForRow;
3721  }
3722 
3723  // Here we will add this view to the tableView and add it to the exposedRows adn columns.
3724  // The view will be deleted if necessary during the next run loop cycle of the table view
3725  // This is how cocoa works
3726  view = [self preparedViewAtColumn:column row:row];
3727 
3728  if ([view superview] !== self)
3729  [self addSubview:view];
3730 
3731  dataViewsForRow[tableColumnUID] = view;
3732 
3733  [_exposedRows addIndex:row];
3734  [_exposedColumns addIndex:column];
3735 
3736  // Make sure to layout the tableView again
3737  [self setNeedsLayout];
3738  }
3739 
3740  return view;
3741 }
3742 
3746 - (CPView)_unarchiveViewWithIdentifier:(CPString)anIdentifier owner:(id)anOwner
3747 {
3748  var cib = [_archivedDataViews objectForKey:anIdentifier];
3749 
3750  if (!cib && !_unavailable_custom_cibs[anIdentifier])
3751  {
3752  var bundle = anOwner ? [CPBundle bundleForClass:[anOwner class]] : [CPBundle mainBundle];
3753  cib = [[CPCib alloc] initWithCibNamed:anIdentifier bundle:bundle];
3754  }
3755 
3756  if (!cib)
3757  {
3758  _unavailable_custom_cibs[anIdentifier] = YES;
3759  return nil;
3760  }
3761 
3762  var objects = [],
3763  load = [cib instantiateCibWithOwner:anOwner topLevelObjects:objects];
3764 
3765  if (!load)
3766  return nil;
3767 
3768  var count = objects.length;
3769 
3770  while (count--)
3771  {
3772  var obj = objects[count];
3773 
3774  if ([obj isKindOfClass:[CPView class]])
3775  {
3776  [obj setIdentifier:anIdentifier];
3777  [obj setAutoresizingMask:CPViewNotSizable];
3778 
3779  return obj;
3780  }
3781  }
3782 
3783  return nil;
3784 }
3785 
3786 - (void)_updateIsViewBased
3787 {
3788  if ([self _delegateRespondsToViewForTableColumn])
3789  _viewForTableColumnRowSelector = @selector(_sendDelegateViewForTableColumn:row:);
3790  else if ([self _delegateRespondsToDataViewForTableColumn])
3791  _viewForTableColumnRowSelector = @selector(_sendDelegateDataViewForTableColumn:row:);
3792 
3793  _isViewBased = (_viewForTableColumnRowSelector !== nil || _archivedDataViews !== nil);
3794 }
3795 
3811 - (void)enumerateAvailableViewsUsingBlock:(Function/*CPView *dataView, CPInteger row, CPInteger column*, @ref stop*/)handler
3812 {
3813  [self reloadData];
3814 
3815  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
3816 
3817  [self _enumerateViewsInRows:_exposedRows columns:_exposedColumns usingBlock:handler];
3818 }
3819 
3824 - (void)_enumerateViewsInRows:(CPIndexSet)rowIndexes columns:(CPIndexSet)columnIndexes usingBlock:(Function/*CPView dataView, CPInteger row, CPInteger column, @ref stop*/)handler
3825 {
3826  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
3827 
3828  [rowIndexes enumerateIndexesUsingBlock:function(rowIndex, stopRow)
3829  {
3830  var dataViewsForRow = _dataViewsForRows[rowIndex];
3831 
3832  if (!dataViewsForRow)
3833  return;
3834 
3835  var row = rowIndex;
3836 
3837  [columnIndexes enumerateIndexesUsingBlock:function(columnIndex, stopCol)
3838  {
3839  var tableColumnUID = [_tableColumns[columnIndex] UID],
3840  view = dataViewsForRow[tableColumnUID];
3841 
3842  if (view)
3843  handler(view, row, columnIndex, stopCol);
3844 
3845  if (stopCol())
3846  stopRow(YES);
3847  }];
3848  }];
3849 }
3850 
3851 - (void)_enumerateViewsInRows:(CPIndexSet)rowIndexes tableColumns:(CPArray)tableColumns usingBlock:(Function/*CPView dataView, CPInteger row, CPtableColumn tableColumn, CPInteger column, @ref stop*/)handler
3852 {
3853  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
3854 
3855  [rowIndexes enumerateIndexesUsingBlock:function(rowIndex, stopRow)
3856  {
3857  var dataViewsForRow = _dataViewsForRows[rowIndex];
3858 
3859  if (!dataViewsForRow)
3860  return;
3861 
3862  var row = rowIndex;
3863 
3864  [tableColumns enumerateObjectsUsingBlock:function(tableColumn, idx, stopCol)
3865  {
3866  var tableColumnUID = [tableColumn UID],
3867  view = dataViewsForRow[tableColumnUID];
3868 
3869  if (view)
3870  handler(view, row, tableColumn, stopCol);
3871 
3872  if (stopCol())
3873  stopRow(YES);
3874  }];
3875  }];
3876 }
3877 
3881 - (void)_enqueueReusableDataView:(CPView)aDataView
3882 {
3883  if (!aDataView)
3884  return;
3885 
3886  // FIXME: yuck!
3887  var identifier = [aDataView identifier];
3888 
3889  if ([aDataView hasThemeState:CPThemeStateSelectedDataView])
3890  [aDataView unsetThemeState:CPThemeStateSelectedDataView];
3891 
3892  if (!_cachedDataViews[identifier])
3893  _cachedDataViews[identifier] = [aDataView];
3894  else
3895  _cachedDataViews[identifier].push(aDataView);
3896 }
3897 
3902 - (void)setFrameSize:(CGSize)aSize
3903 {
3904  [super setFrameSize:aSize];
3905 
3906  if (_headerView)
3907  [_headerView setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), CGRectGetHeight([_headerView frame]))];
3908 
3909  _exposedRect = nil;
3910 }
3911 
3915 - (void)setFrameOrigin:(CGPoint)aPoint
3916 {
3917  [super setFrameOrigin:aPoint];
3918 
3919  _exposedRect = nil;
3920 }
3921 
3925 - (void)setBoundsOrigin:(CGPoint)aPoint
3926 {
3927  [super setBoundsOrigin:aPoint];
3928 
3929  _exposedRect = nil;
3930 }
3931 
3935 - (void)setBoundsSize:(CGSize)aSize
3936 {
3937  [super setBoundsSize:aSize];
3938 
3939  _exposedRect = nil;
3940 }
3941 
3945 - (void)setNeedsDisplay:(BOOL)aFlag
3946 {
3947  [super setNeedsDisplay:aFlag];
3948  [_tableDrawView setNeedsDisplay:aFlag];
3949 
3950  [[self headerView] setNeedsDisplay:YES];
3951 }
3952 
3956 - (void)setNeedsLayout
3957 {
3958  [super setNeedsLayout];
3959  [[self headerView] setNeedsLayout];
3960 }
3961 
3965 - (BOOL)_isFocused
3966 {
3967  return [[self window] isKeyWindow] && ([[self window] firstResponder] === self || _editingRow !== CPNotFound);
3968 }
3969 
3973 - (void)_drawRect:(CGRect)aRect
3974 {
3975  // FIX ME: All three of these methods will likely need to be rewritten for 1.0
3976  // We've got grid drawing in highlightSelection and crap everywhere.
3977 
3978  var exposedRect = [self exposedRect];
3979 
3980  [self drawBackgroundInClipRect:exposedRect];
3981  [self highlightSelectionInClipRect:exposedRect];
3982  [self drawGridInClipRect:exposedRect];
3983 
3984  if (_implementsCustomDrawRow)
3985  [self _drawRows:_exposedRows clipRect:exposedRect];
3986 }
3987 
3993 - (void)drawBackgroundInClipRect:(CGRect)aRect
3994 {
3995  if (!_usesAlternatingRowBackgroundColors)
3996  return;
3997 
3998  var rowColors = [self alternatingRowBackgroundColors],
3999  colorCount = [rowColors count];
4000 
4001  if (colorCount === 0)
4002  return;
4003 
4004  var context = [[CPGraphicsContext currentContext] graphicsPort];
4005 
4006  if (colorCount === 1)
4007  {
4008  CGContextSetFillColor(context, rowColors[0]);
4009  CGContextFillRect(context, aRect);
4010 
4011  return;
4012  }
4013 
4014  var exposedRows = [self _exposedRowsInRect:aRect],
4015  firstRow = FLOOR(exposedRows.location / colorCount) * colorCount,
4016  lastRow = CPMaxRange(exposedRows) - 1,
4017  colorIndex = 0,
4018  groupRowRects = [];
4019 
4020  //loop through each color so we only draw once for each color
4021  while (colorIndex < colorCount)
4022  {
4023  CGContextBeginPath(context);
4024 
4025  for (var row = firstRow + colorIndex; row <= lastRow; row += colorCount)
4026  {
4027  // if it's not a group row draw it otherwise we draw it later
4028  if (![_groupRows containsIndex:row])
4029  CGContextAddRect(context, CGRectIntersection(aRect, [self _rectOfRow:row checkRange:NO]));
4030  else
4031  groupRowRects.push(CGRectIntersection(aRect, [self _rectOfRow:row checkRange:NO]));
4032  }
4033 
4034  CGContextClosePath(context);
4035 
4036  CGContextSetFillColor(context, rowColors[colorIndex]);
4037  CGContextFillPath(context);
4038 
4039  colorIndex++;
4040  }
4041 
4042  [self _drawGroupRowsForRects:groupRowRects];
4043 }
4044 
4049 - (void)drawGridInClipRect:(CGRect)aRect
4050 {
4051  var context = [[CPGraphicsContext currentContext] graphicsPort],
4052  gridStyleMask = [self gridStyleMask],
4053  lineThickness = [self currentValueForThemeAttribute:@"grid-line-thickness"];
4054 
4056  return;
4057 
4058  CGContextBeginPath(context);
4059 
4060  if (gridStyleMask & CPTableViewSolidHorizontalGridLineMask)
4061  {
4062  var exposedRows = [self _exposedRowsInRect:aRect],
4063  row = exposedRows.location,
4064  lastRow = CPMaxRange(exposedRows) - 1,
4065  rowY = -lineThickness / 2,
4066  minX = CGRectGetMinX(aRect),
4067  maxX = CGRectGetMaxX(aRect);
4068 
4069  for (; row <= lastRow; ++row)
4070  {
4071  // grab each row rect and add the top and bottom lines
4072  var rowRect = [self _rectOfRow:row checkRange:NO],
4073  rowY = CGRectGetMaxY(rowRect) - lineThickness / 2;
4074 
4075  CGContextMoveToPoint(context, minX, rowY);
4076  CGContextAddLineToPoint(context, maxX, rowY);
4077  }
4078 
4079  if (_rowHeight > 0.0)
4080  {
4081  var rowHeight = FULL_ROW_HEIGHT(),
4082  totalHeight = CGRectGetMaxY(aRect) - lineThickness / 2;
4083 
4084  while (rowY < totalHeight)
4085  {
4086  rowY += rowHeight;
4087 
4088  CGContextMoveToPoint(context, minX, rowY);
4089  CGContextAddLineToPoint(context, maxX, rowY);
4090  }
4091  }
4092  }
4093 
4094  if (gridStyleMask & CPTableViewSolidVerticalGridLineMask)
4095  {
4096  var exposedColumnIndexes = [self columnIndexesInRect:aRect],
4097  columnsArray = [];
4098 
4099  [exposedColumnIndexes getIndexes:columnsArray maxCount:-1 inIndexRange:nil];
4100 
4101  var columnArrayIndex = 0,
4102  columnArrayCount = columnsArray.length,
4103  minY = CGRectGetMinY(aRect),
4104  maxY = CGRectGetMaxY(aRect);
4105 
4106  for (; columnArrayIndex < columnArrayCount; ++columnArrayIndex)
4107  {
4108  var columnRect = [self rectOfColumn:columnsArray[columnArrayIndex]],
4109  columnX = CGRectGetMaxX(columnRect) - lineThickness / 2;
4110 
4111  CGContextMoveToPoint(context, columnX, minY);
4112  CGContextAddLineToPoint(context, columnX, maxY);
4113  }
4114  }
4115 
4116  CGContextClosePath(context);
4117  CGContextSetStrokeColor(context, [self gridColor]);
4118  CGContextSetLineWidth(context, lineThickness);
4119  CGContextStrokePath(context);
4120 }
4121 
4127 - (void)highlightSelectionInClipRect:(CGRect)aRect
4128 {
4129  if (_selectionHighlightStyle === CPTableViewSelectionHighlightStyleNone)
4130  return;
4131 
4132  var context = [[CPGraphicsContext currentContext] graphicsPort],
4133  indexes = [],
4134  rectSelector = @selector(rectOfRow:);
4135 
4136  if ([_selectedRowIndexes count] >= 1)
4137  {
4138  var exposedRows = [CPIndexSet indexSetWithIndexesInRange:[self rowsInRect:aRect]],
4139  firstRow = [exposedRows firstIndex],
4140  exposedRange = CPMakeRange(firstRow, [exposedRows lastIndex] - firstRow + 1);
4141 
4142  [_selectedRowIndexes getIndexes:indexes maxCount:-1 inIndexRange:exposedRange];
4143  }
4144  else if ([_selectedColumnIndexes count] >= 1)
4145  {
4146  rectSelector = @selector(rectOfColumn:);
4147 
4148  var exposedColumns = [self columnIndexesInRect:aRect],
4149  firstColumn = [exposedColumns firstIndex],
4150  exposedRange = CPMakeRange(firstColumn, [exposedColumns lastIndex] - firstColumn + 1);
4151 
4152  [_selectedColumnIndexes getIndexes:indexes maxCount:-1 inIndexRange:exposedRange];
4153  }
4154 
4155  var count,
4156  count2 = count = [indexes count];
4157 
4158  if (!count)
4159  return;
4160 
4161  var drawGradient = (CPFeatureIsCompatible(CPHTMLCanvasFeature) && _selectionHighlightStyle === CPTableViewSelectionHighlightStyleSourceList && [_selectedRowIndexes count] >= 1),
4162  deltaHeight = 0.5 * (_gridStyleMask & CPTableViewSolidHorizontalGridLineMask),
4163  focused = [self _isFocused];
4164 
4165  CGContextBeginPath(context);
4166 
4167  if (drawGradient)
4168  {
4169  var gradientCache = focused ? [self selectionGradientColors] : [self unfocusedSelectionGradientColors],
4170  topLineColor = [gradientCache objectForKey:CPSourceListTopLineColor],
4171  bottomLineColor = [gradientCache objectForKey:CPSourceListBottomLineColor],
4172  gradientColor = [gradientCache objectForKey:CPSourceListGradient];
4173  }
4174 
4175  var normalSelectionHighlightColor = focused ? [self selectionHighlightColor] : [self unfocusedSelectionHighlightColor];
4176 
4177  // don't do these lookups if there are no group rows
4178  if ([_groupRows count])
4179  {
4180  var topGroupLineColor = [CPColor colorWithCalibratedWhite:212.0 / 255.0 alpha:1.0],
4181  bottomGroupLineColor = [CPColor colorWithCalibratedWhite:185.0 / 255.0 alpha:1.0],
4182  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);
4183  }
4184 
4185  while (count--)
4186  {
4187  var currentIndex = indexes[count],
4188  rowRect = CGRectIntersection(objj_msgSend(self, rectSelector, currentIndex), aRect);
4189 
4190  // group rows get the same highlight style as other rows if they're source list...
4191  if (!drawGradient)
4192  var shouldUseGroupGradient = [_groupRows containsIndex:currentIndex];
4193 
4194  if (drawGradient || shouldUseGroupGradient)
4195  {
4196  var minX = CGRectGetMinX(rowRect),
4197  minY = CGRectGetMinY(rowRect),
4198  maxX = CGRectGetMaxX(rowRect),
4199  maxY = CGRectGetMaxY(rowRect) - deltaHeight;
4200 
4201  if (!drawGradient)
4202  {
4203  //If there is no source list gradient we need to close the selection path and fill it now
4204  [normalSelectionHighlightColor setFill];
4205  CGContextClosePath(context);
4206  CGContextFillPath(context);
4207  CGContextBeginPath(context);
4208  }
4209 
4210  CGContextAddRect(context, rowRect);
4211 
4212  CGContextDrawLinearGradient(context, (shouldUseGroupGradient) ? gradientGroupColor : gradientColor, rowRect.origin, CGPointMake(minX, maxY), 0);
4213 
4214  CGContextBeginPath(context);
4215  CGContextMoveToPoint(context, minX, minY + .5);
4216  CGContextAddLineToPoint(context, maxX, minY + .5);
4217  CGContextSetStrokeColor(context, (shouldUseGroupGradient) ? topGroupLineColor : topLineColor);
4218  CGContextStrokePath(context);
4219 
4220  CGContextBeginPath(context);
4221  CGContextMoveToPoint(context, minX, maxY - .5);
4222  CGContextAddLineToPoint(context, maxX, maxY - .5);
4223  CGContextSetStrokeColor(context, (shouldUseGroupGradient) ? bottomGroupLineColor : bottomLineColor);
4224  CGContextStrokePath(context);
4225  }
4226  else
4227  {
4228  var radius = [self currentValueForThemeAttribute:@"selection-radius"];
4229 
4230  if (radius > 0)
4231  {
4232  var minX = CGRectGetMinX(rowRect),
4233  maxX = CGRectGetMaxX(rowRect),
4234  minY = CGRectGetMinY(rowRect),
4235  maxY = CGRectGetMaxY(rowRect);
4236 
4237  CGContextMoveToPoint(context, minX + radius, minY);
4238  CGContextAddArcToPoint(context, maxX, minY, maxX, minY + radius, radius);
4239  CGContextAddArcToPoint(context, maxX, maxY, maxX - radius, maxY, radius);
4240  CGContextAddArcToPoint(context, minX, maxY, minX, maxY - radius, radius);
4241  CGContextAddArcToPoint(context, minX, minY, minX + radius, minY, radius);
4242  }
4243  else
4244  CGContextAddRect(context, rowRect);
4245  }
4246  }
4247 
4248  CGContextClosePath(context);
4249 
4250  if (!drawGradient)
4251  {
4252  [normalSelectionHighlightColor setFill];
4253  CGContextFillPath(context);
4254  }
4255 
4256  CGContextBeginPath(context);
4257 
4258  var gridStyleMask = [self gridStyleMask];
4259 
4260  for (var i = 0; i < count2; i++)
4261  {
4262  var rect = objj_msgSend(self, rectSelector, indexes[i]),
4263  minX = CGRectGetMinX(rect) - 0.5,
4264  maxX = CGRectGetMaxX(rect) - 0.5,
4265  minY = CGRectGetMinY(rect) - 0.5,
4266  maxY = CGRectGetMaxY(rect) - 0.5;
4267 
4268  if ([_selectedRowIndexes count] >= 1 && gridStyleMask & CPTableViewSolidVerticalGridLineMask)
4269  {
4270  var exposedColumns = [self columnIndexesInRect:aRect],
4271  exposedColumnIndexes = [],
4272  firstExposedColumn = [exposedColumns firstIndex],
4273  exposedRange = CPMakeRange(firstExposedColumn, [exposedColumns lastIndex] - firstExposedColumn + 1);
4274 
4275  [exposedColumns getIndexes:exposedColumnIndexes maxCount:-1 inIndexRange:exposedRange];
4276 
4277  var exposedColumnCount = [exposedColumnIndexes count];
4278 
4279  for (var c = firstExposedColumn; c < exposedColumnCount; c++)
4280  {
4281  var colRect = [self rectOfColumn:exposedColumnIndexes[c]],
4282  colX = CGRectGetMaxX(colRect) + 0.5;
4283 
4284  CGContextMoveToPoint(context, colX, minY);
4285  CGContextAddLineToPoint(context, colX, maxY);
4286  }
4287  }
4288 
4289  //if the row after the current row is not selected then there is no need to draw the bottom grid line white.
4290  if ([indexes containsObject:indexes[i] + 1])
4291  {
4292  CGContextMoveToPoint(context, minX, maxY);
4293  CGContextAddLineToPoint(context, maxX, maxY);
4294  }
4295  }
4296 
4297  CGContextClosePath(context);
4298  CGContextSetStrokeColor(context, [self currentValueForThemeAttribute:@"highlighted-grid-color"]);
4299  CGContextStrokePath(context);
4300 }
4301 
4307 - (void)_drawGroupRowsForRects:(CPArray)rects
4308 {
4309  if ((CPFeatureIsCompatible(CPHTMLCanvasFeature) && _selectionHighlightStyle === CPTableViewSelectionHighlightStyleSourceList) || !rects.length)
4310  return;
4311 
4312  var context = [[CPGraphicsContext currentContext] graphicsPort],
4313  i = rects.length;
4314 
4315  CGContextBeginPath(context);
4316 
4317  var gradientCache = [self selectionGradientColors],
4318  topLineColor = [CPColor colorWithHexString:"d3d3d3"],
4319  bottomLineColor = [CPColor colorWithHexString:"bebebd"],
4320  gradientColor = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(), [220.0 / 255.0, 220.0 / 255.0, 220.0 / 255.0, 1.0,
4321  199.0 / 255.0, 199.0 / 255.0, 199.0 / 255.0, 1.0], [0, 1], 2),
4322  drawGradient = YES;
4323 
4324  while (i--)
4325  {
4326  var rowRect = rects[i];
4327 
4328  CGContextAddRect(context, rowRect);
4329 
4330  if (drawGradient)
4331  {
4332  var minX = CGRectGetMinX(rowRect),
4333  minY = CGRectGetMinY(rowRect),
4334  maxX = CGRectGetMaxX(rowRect),
4335  maxY = CGRectGetMaxY(rowRect);
4336 
4337  CGContextDrawLinearGradient(context, gradientColor, rowRect.origin, CGPointMake(minX, maxY), 0);
4338 
4339  CGContextBeginPath(context);
4340  CGContextMoveToPoint(context, minX, minY);
4341  CGContextAddLineToPoint(context, maxX, minY);
4342  CGContextSetStrokeColor(context, topLineColor);
4343  CGContextStrokePath(context);
4344 
4345  CGContextBeginPath(context);
4346  CGContextMoveToPoint(context, minX, maxY);
4347  CGContextAddLineToPoint(context, maxX, maxY - 1);
4348  CGContextSetStrokeColor(context, bottomLineColor);
4349  CGContextStrokePath(context);
4350  }
4351  }
4352 }
4353 
4357 - (void)_drawRows:(CPIndexSet)rowsIndexes clipRect:(CGRect)clipRect
4358 {
4359  var row = [rowsIndexes firstIndex];
4360 
4361  while (row !== CPNotFound)
4362  {
4363  [self drawRow:row clipRect:CGRectIntersection(clipRect, [self rectOfRow:row])];
4364  row = [rowsIndexes indexGreaterThanIndex:row];
4365  }
4366 }
4367 
4374 - (void)drawRow:(CPInteger)row clipRect:(CGRect)rect
4375 {
4376  // This method does currently nothing in cappuccino. Can be overridden by subclasses.
4377 
4378 }
4379 
4383 - (void)layoutSubviews
4384 {
4385  [self load];
4386 }
4387 
4391 - (void)viewWillMoveToSuperview:(CPView)aView
4392 {
4393  if ([aView isKindOfClass:[CPClipView class]])
4394  {
4395  _observedClipView = aView;
4396  }
4397  else
4398  {
4399  [self _stopObservingClipView];
4400  _observedClipView = nil;
4401  }
4402 
4403  [super viewWillMoveToSuperview:aView];
4404 }
4405 
4409 - (void)superviewBoundsChanged:(CPNotification)aNotification
4410 {
4411  _exposedRect = nil;
4412 
4413  [self setNeedsDisplay:YES];
4414  [self setNeedsLayout];
4415 }
4416 
4420 - (void)superviewFrameChanged:(CPNotification)aNotification
4421 {
4422  _exposedRect = nil;
4423 
4424  [self tile];
4425 }
4426 
4427 /*
4428  @ignore
4429 */
4430 - (BOOL)tracksMouseOutsideOfFrame
4431 {
4432  return YES;
4433 }
4434 
4435 /*
4436  @ignore
4437 */
4438 - (BOOL)startTrackingAt:(CGPoint)aPoint
4439 {
4440  // Try to become the first responder, but if we can't, that's okay.
4441  [[self window] makeFirstResponder:self];
4442 
4443  var row = [self rowAtPoint:aPoint];
4444 
4445  _clickedRow = row;
4446  _clickedColumn = [self columnAtPoint:aPoint];
4447 
4448  // If the user clicks outside a row then deselect everything.
4449  if (row < 0 && _allowsEmptySelection)
4450  {
4451  if ([self _sendDelegateSelectionShouldChangeInTableView])
4452  {
4453  var indexSet = [self _sendDelegateSelectionIndexesForProposedSelection:[CPIndexSet indexSet]];
4454 
4455  [self _noteSelectionIsChanging];
4456  [self selectRowIndexes:indexSet byExtendingSelection:NO];
4457  }
4458  }
4459 
4460  if ([self mouseDownFlags] & CPShiftKeyMask)
4461  _selectionAnchorRow = (ABS([_selectedRowIndexes firstIndex] - row) < ABS([_selectedRowIndexes lastIndex] - row)) ?
4462  [_selectedRowIndexes firstIndex] : [_selectedRowIndexes lastIndex];
4463  else
4464  _selectionAnchorRow = row;
4465 
4466  //set ivars for startTrackingPoint and time...
4467  _startTrackingPoint = aPoint;
4468  _startTrackingTimestamp = new Date();
4469 
4470  if ([self _dataSourceRespondsToSetObjectValueForTableColumnRow])
4471  _trackingPointMovedOutOfClickSlop = NO;
4472 
4473  // if the table has drag support then we use mouseUp to select a single row.
4474  // otherwise it uses mouse down.
4475  if (row >= 0 && !([self _dataSourceRespondsToWriteRowsWithIndexesToPasteboard]))
4476  [self _updateSelectionWithMouseAtRow:row];
4477 
4478  return YES;
4479 }
4480 
4484 - (CPMenu)menuForEvent:(CPEvent)theEvent
4485 {
4486  if (!([self _delegateRespondsToMenuForTableColumnRow]))
4487  return [super menuForEvent:theEvent];
4488 
4489  var location = [self convertPoint:[theEvent locationInWindow] fromView:nil],
4490  row = [self rowAtPoint:location],
4491  column = [self columnAtPoint:location],
4492  tableColumn = [[self tableColumns] objectAtIndex:column];
4493 
4494  return [self _sendDelegateMenuForTableColumn:tableColumn row:row];
4495 }
4496 
4497 /*
4498  @ignore
4499 */
4500 - (void)trackMouse:(CPEvent)anEvent
4501 {
4502  // Prevent CPControl from eating the mouse events when we are in a drag session
4503  if (![_draggedRowIndexes count])
4504  {
4505  [self autoscroll:anEvent];
4506  [super trackMouse:anEvent];
4507  }
4508  else
4509  [CPApp sendEvent:anEvent];
4510 
4511  if ([anEvent type] == CPLeftMouseUp)
4512  {
4513  _clickedRow = CPNotFound;
4514  _clickedColumn = CPNotFound;
4515  }
4516 }
4517 
4518 /*
4519  @ignore
4520 */
4521 - (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
4522 {
4523  var row = [self rowAtPoint:aPoint];
4524 
4525  _clickedRow = row;
4526  _clickedColumn = [self columnAtPoint:aPoint];
4527 
4528  // begin the drag is the datasource lets us, we've move at least +-3px vertical or horizontal,
4529  // or we're dragging from selected rows and we haven't begun a drag session
4530  if (!_isSelectingSession && [self _dataSourceRespondsToWriteRowsWithIndexesToPasteboard])
4531  {
4532  if (row >= 0 && (ABS(_startTrackingPoint.x - aPoint.x) > 3 || (_verticalMotionCanDrag && ABS(_startTrackingPoint.y - aPoint.y) > 3)) ||
4533  ([_selectedRowIndexes containsIndex:row]))
4534  {
4535  if ([_selectedRowIndexes containsIndex:row])
4536  _draggedRowIndexes = [[CPIndexSet alloc] initWithIndexSet:_selectedRowIndexes];
4537  else
4538  _draggedRowIndexes = [CPIndexSet indexSetWithIndex:row];
4539 
4540  //ask the datasource for the data
4541  var pboard = [CPPasteboard pasteboardWithName:CPDragPboard];
4542 
4543  if ([self canDragRowsWithIndexes:_draggedRowIndexes atPoint:aPoint] && [self _sendDataSourceWriteRowsWithIndexes:_draggedRowIndexes toPasteboard:pboard])
4544  {
4545  var currentEvent = [CPApp currentEvent],
4546  offset = CGPointMakeZero(),
4547  tableColumns = [_tableColumns objectsAtIndexes:_exposedColumns];
4548 
4549  // We deviate from the default Cocoa implementation here by asking for a view in stead of an image
4550  // We support both, but the view preferred over the image because we can mimic the rows we are dragging
4551  // by re-creating the data views for the dragged rows
4552  var view = [self dragViewForRowsWithIndexes:_draggedRowIndexes
4553  tableColumns:tableColumns
4554  event:currentEvent
4555  offset:offset];
4556 
4557  if (!view)
4558  {
4559  var image = [self dragImageForRowsWithIndexes:_draggedRowIndexes
4560  tableColumns:tableColumns
4561  event:currentEvent
4562  offset:offset];
4563  view = [[CPImageView alloc] initWithFrame:CGRectMake(0, 0, [image size].width, [image size].height)];
4564  [view setImage:image];
4565  }
4566 
4567  var bounds = [view bounds],
4568  viewLocation = CGPointMake(aPoint.x - CGRectGetWidth(bounds) / 2 + offset.x, aPoint.y - CGRectGetHeight(bounds) / 2 + offset.y);
4569  [self dragView:view at:viewLocation offset:CGPointMakeZero() event:[CPApp currentEvent] pasteboard:pboard source:self slideBack:YES];
4570  _startTrackingPoint = nil;
4571 
4572  return NO;
4573  }
4574 
4575  // The delegate disallowed the drag so clear the dragged row indexes
4576  _draggedRowIndexes = [CPIndexSet indexSet];
4577  }
4578  else if (ABS(_startTrackingPoint.x - aPoint.x) < 5 && ABS(_startTrackingPoint.y - aPoint.y) < 5)
4579  return YES;
4580  }
4581 
4582  _isSelectingSession = YES;
4583  if (row >= 0 && row !== _lastTrackedRowIndex)
4584  {
4585  _lastTrackedRowIndex = row;
4586  [self _updateSelectionWithMouseAtRow:row];
4587  }
4588 
4589  if ([self _dataSourceRespondsToSetObjectValueForTableColumnRow]
4590  && !_trackingPointMovedOutOfClickSlop)
4591  {
4592  var CLICK_SPACE_DELTA = 5.0; // Stolen from AppKit/Platform/DOM/CPPlatformWindow+DOM.j
4593  if (ABS(aPoint.x - _startTrackingPoint.x) > CLICK_SPACE_DELTA
4594  || ABS(aPoint.y - _startTrackingPoint.y) > CLICK_SPACE_DELTA)
4595  {
4596  _trackingPointMovedOutOfClickSlop = YES;
4597  }
4598  }
4599 
4600  return YES;
4601 }
4602 
4606 - (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
4607 {
4608  _isSelectingSession = NO;
4609 
4610  var CLICK_TIME_DELTA = 1000,
4611  columnIndex = -1,
4612  column,
4613  rowIndex,
4614  shouldEdit = YES;
4615 
4616  _clickedRow = [self rowAtPoint:aPoint];
4617  _clickedColumn = [self columnAtPoint:aPoint];
4618 
4619  if ([self _dataSourceRespondsToWriteRowsWithIndexesToPasteboard])
4620  {
4621  rowIndex = _clickedRow;
4622 
4623  if (rowIndex !== -1)
4624  {
4625  if ([_draggedRowIndexes count] > 0)
4626  {
4627  _draggedRowIndexes = [CPIndexSet indexSet];
4628  return;
4629  }
4630  // if the table has drag support then we use mouseUp to select a single row.
4631  _previouslySelectedRowIndexes = [_selectedRowIndexes copy];
4632  [self _updateSelectionWithMouseAtRow:rowIndex];
4633  }
4634  }
4635 
4636  // Accept either tableView:setObjectValue:forTableColumn:row: delegate method, or a binding.
4637  if (!_isViewBased && mouseIsUp
4638  && !_trackingPointMovedOutOfClickSlop
4639  && ([[CPApp currentEvent] clickCount] > 1)
4640  && ([self _dataSourceRespondsToSetObjectValueForTableColumnRow]
4641  || [self infoForBinding:@"content"]))
4642  {
4643  columnIndex = _clickedColumn;
4644 
4645  if (columnIndex !== -1)
4646  {
4647  column = _tableColumns[columnIndex];
4648 
4649  if ([column isEditable])
4650  {
4651  rowIndex = [self rowAtPoint:aPoint];
4652 
4653  if (rowIndex !== -1)
4654  {
4655  if ([self _sendDelegateShouldEditTableColumn:column row:rowIndex])
4656  {
4657  [self editColumn:columnIndex row:rowIndex withEvent:nil select:YES];
4658  return;
4659  }
4660  }
4661  }
4662  }
4663 
4664  } //end of editing conditional
4665 
4666  //double click actions
4667  if ([[CPApp currentEvent] clickCount] === 2 && _doubleAction)
4668  [self sendAction:_doubleAction to:_target];
4669 }
4670 
4671 /*
4672  @ignore
4673 */
4674 - (CPDragOperation)draggingEntered:(id)sender
4675 {
4676  var location = [self convertPoint:[sender draggingLocation] fromView:nil],
4677  dropOperation = [self _proposedDropOperationAtPoint:location],
4678  row = [self _proposedRowAtPoint:location];
4679 
4680  if (_retargetedDropRow !== nil)
4681  row = _retargetedDropRow;
4682 
4683  var draggedTypes = [self registeredDraggedTypes],
4684  count = [draggedTypes count],
4685  i = 0;
4686 
4687  for (; i < count; i++)
4688  {
4689  if ([[[sender draggingPasteboard] types] containsObject:[draggedTypes objectAtIndex: i]])
4690  return [self _sendDataSourceValidateDrop:sender proposedRow:row proposedDropOperation:dropOperation];
4691  }
4692 
4693  return CPDragOperationNone;
4694 }
4695 
4696 /*
4697  @ignore
4698 */
4699 - (void)draggingExited:(id)sender
4700 {
4701  [_dropOperationFeedbackView removeFromSuperview];
4702 }
4703 
4704 /*
4705  @ignore
4706 */
4707 - (void)draggingEnded:(id)sender
4708 {
4709  [self _draggingEnded];
4710 }
4711 
4715 - (void)_draggingEnded
4716 {
4717  _retargetedDropOperation = nil;
4718  _retargetedDropRow = nil;
4719  _draggedRowIndexes = [CPIndexSet indexSet];
4720  [_dropOperationFeedbackView removeFromSuperview];
4721 }
4722 
4723 /*
4724  @ignore
4725 */
4726 - (BOOL)wantsPeriodicDraggingUpdates
4727 {
4728  return YES;
4729 }
4730 
4731 /*
4732  @ignore
4733 */
4734 - (CPTableViewDropOperation)_proposedDropOperationAtPoint:(CGPoint)theDragPoint
4735 {
4736  if (_retargetedDropOperation !== nil)
4737  return _retargetedDropOperation;
4738 
4739  var row = [self _proposedRowAtPoint:theDragPoint],
4740  rowRect = [self rectOfRow:row];
4741 
4742  // If there is no (the default) or too little inter-cell spacing we create some room for the CPTableViewDropAbove indicator
4743  // This probably doesn't work if the row height is smaller than or around 5.0
4744  if ([self intercellSpacing].height < 5.0)
4745  rowRect = CGRectInset(rowRect, 0.0, 5.0 - [self intercellSpacing].height);
4746 
4747  // If the altered row rect contains the drag point we show the drop on
4748  // We don't show the drop on indicator if we are dragging below the last row
4749  // in that case we always want to show the drop above indicator
4750  if (CGRectContainsPoint(rowRect, theDragPoint) && row < _numberOfRows)
4751  return CPTableViewDropOn;
4752 
4753  return CPTableViewDropAbove;
4754 }
4755 
4756 /*
4757  @ignore
4758 */
4759 - (CPInteger)_proposedRowAtPoint:(CGPoint)dragPoint
4760 {
4761  var row = [self rowAtPoint:dragPoint],
4762  // Determine if the mouse is currently closer to this row or the row below it
4763  lowerRow = row + 1,
4764  rect = [self rectOfRow:row],
4765  bottomPoint = CGRectGetMaxY(rect),
4766  bottomThirty = bottomPoint - ((bottomPoint - CGRectGetMinY(rect)) * 0.3),
4767  numberOfRows = [self numberOfRows];
4768 
4769  if (row < 0)
4770  row = (CGRectGetMaxY(rect) < dragPoint.y) ? numberOfRows : row;
4771  else if (dragPoint.y > MAX(bottomThirty, bottomPoint - 6))
4772  row = lowerRow;
4773 
4774  row = MIN(numberOfRows, row);
4775 
4776  return row;
4777 }
4778 
4782 - (CGRect)_rectForDropHighlightViewOnRow:(CPInteger)theRowIndex
4783 {
4784  if (theRowIndex >= [self numberOfRows])
4785  theRowIndex = [self numberOfRows] - 1;
4786 
4787  return [self _rectOfRow:theRowIndex checkRange:NO];
4788 }
4789 
4793 - (CGRect)_rectForDropHighlightViewBetweenUpperRow:(CPInteger)theUpperRowIndex andLowerRow:(CPInteger)theLowerRowIndex offset:(CGPoint)theOffset
4794 {
4795  if (theLowerRowIndex > [self numberOfRows])
4796  theLowerRowIndex = [self numberOfRows];
4797 
4798  return [self _rectOfRow:theLowerRowIndex checkRange:NO];
4799 }
4800 
4804 - (CPDragOperation)draggingUpdated:(id)sender
4805 {
4806  _retargetedDropRow = nil;
4807  _retargetedDropOperation = nil;
4808 
4809  var location = [self convertPoint:[sender draggingLocation] fromView:nil],
4810  dropOperation = [self _proposedDropOperationAtPoint:location],
4811  numberOfRows = [self numberOfRows],
4812  row = [self _proposedRowAtPoint:location],
4813  dragOperation = [self _sendDataSourceValidateDrop:sender proposedRow:row proposedDropOperation:dropOperation];
4814 
4815  if (_retargetedDropRow !== nil)
4816  row = _retargetedDropRow;
4817 
4818  if (_retargetedDropOperation !== nil)
4819  dropOperation = _retargetedDropOperation;
4820 
4821  if (dropOperation === CPTableViewDropOn && row >= numberOfRows)
4822  row = numberOfRows - 1;
4823 
4824  var rect = CGRectMakeZero();
4825 
4826  if (row === -1)
4827  rect = [self exposedRect];
4828  else if (dropOperation === CPTableViewDropAbove)
4829  rect = [self _rectForDropHighlightViewBetweenUpperRow:row - 1 andLowerRow:row offset:location];
4830  else
4831  rect = [self _rectForDropHighlightViewOnRow:row];
4832 
4833  [_dropOperationFeedbackView setDropOperation:row !== -1 ? dropOperation : CPDragOperationNone];
4834  [_dropOperationFeedbackView setHidden:(dragOperation == CPDragOperationNone)];
4835  [_dropOperationFeedbackView setFrame:rect];
4836  [_dropOperationFeedbackView setCurrentRow:row];
4837  [self addSubview:_dropOperationFeedbackView];
4838 
4839  return dragOperation;
4840 }
4841 
4842 /*
4843  @ignore
4844 */
4845 - (BOOL)prepareForDragOperation:(id)sender
4846 {
4847  // FIX ME: is there anything else that needs to happen here?
4848  // actual validation is called in draggingUpdated:
4849  [_dropOperationFeedbackView removeFromSuperview];
4850 
4851  return [self _dataSourceRespondsToValidateDropProposedRowProposedDropOperation];
4852 }
4853 
4854 /*
4855  @ignore
4856 */
4857 - (BOOL)performDragOperation:(id)sender
4858 {
4859  var location = [self convertPoint:[sender draggingLocation] fromView:nil],
4860  operation = [self _proposedDropOperationAtPoint:location],
4861  row = _retargetedDropRow;
4862 
4863  if (row === nil)
4864  var row = [self _proposedRowAtPoint:location];
4865 
4866  return [self _sendDataSourceAcceptDrop:sender row:row dropOperation:operation];
4867 }
4868 
4869 /*
4870  @ignore
4871 */
4872 - (void)concludeDragOperation:(id)sender
4873 {
4874  [self _reloadDataViews];
4875 }
4876 
4877 /*
4878  @ignore
4879  We're using this because we drag views instead of images so we can get the rows themselves to actually drag.
4880 */
4881 - (void)draggedView:(CPImage)aView endedAt:(CGPoint)aLocation operation:(CPDragOperation)anOperation
4882 {
4883  [self _draggingEnded];
4884  [self draggedImage:aView endedAt:aLocation operation:anOperation];
4885 }
4886 
4890 - (void)_updateSelectionWithMouseAtRow:(CPInteger)aRow
4891 {
4892  //check to make sure the row exists
4893  if (aRow < 0)
4894  return;
4895 
4896  var newSelection,
4897  shouldExtendSelection = NO;
4898 
4899  // If cmd/ctrl was held down XOR the old selection with the proposed selection
4900  if ([self mouseDownFlags] & (CPCommandKeyMask | CPControlKeyMask | CPAlternateKeyMask))
4901  {
4902  if ([_selectedRowIndexes containsIndex:aRow])
4903  {
4904  newSelection = [_selectedRowIndexes copy];
4905 
4906  [newSelection removeIndex:aRow];
4907  }
4908  else if (_allowsMultipleSelection)
4909  {
4910  newSelection = [_selectedRowIndexes copy];
4911 
4912  [newSelection addIndex:aRow];
4913  }
4914  else
4915  newSelection = [CPIndexSet indexSetWithIndex:aRow];
4916  }
4917  else if (_allowsMultipleSelection)
4918  {
4919  if (_selectionAnchorRow == CPNotFound)
4920  _selectionAnchorRow = [self numberOfRows] - 1;
4921 
4922  newSelection = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(MIN(aRow, _selectionAnchorRow), ABS(aRow - _selectionAnchorRow) + 1)];
4923  shouldExtendSelection = [self mouseDownFlags] & CPShiftKeyMask &&
4924  ((_lastSelectedRow == [_selectedRowIndexes lastIndex] && aRow > _lastSelectedRow) ||
4925  (_lastSelectedRow == [_selectedRowIndexes firstIndex] && aRow < _lastSelectedRow));
4926  }
4927  else if (aRow >= 0 && aRow < _numberOfRows)
4928  newSelection = [CPIndexSet indexSetWithIndex:aRow];
4929  else
4930  newSelection = [CPIndexSet indexSet];
4931 
4932  if ([newSelection isEqualToIndexSet:_selectedRowIndexes])
4933  return;
4934 
4935  if (![self _sendDelegateSelectionShouldChangeInTableView])
4936  return;
4937 
4938  newSelection = [self _sendDelegateSelectionIndexesForProposedSelection:newSelection];
4939 
4940  if (![self _delegateRespondsToSelectionIndexesForProposedSelection] && [self _delegateRespondsToShouldSelectRow])
4941  {
4942  var indexArray = [];
4943 
4944  [newSelection getIndexes:indexArray maxCount:-1 inIndexRange:nil];
4945 
4946  var indexCount = indexArray.length;
4947 
4948  while (indexCount--)
4949  {
4950  var index = indexArray[indexCount];
4951 
4952  if (![self _sendDelegateShouldSelectRow:index])
4953  [newSelection removeIndex:index];
4954  }
4955 
4956  // as per cocoa
4957  if ([newSelection count] === 0)
4958  return;
4959  }
4960 
4961  // if empty selection is not allowed and the new selection has nothing selected, abort
4962  if (!_allowsEmptySelection && [newSelection count] === 0)
4963  return;
4964 
4965  if ([newSelection isEqualToIndexSet:_selectedRowIndexes])
4966  return;
4967 
4968  [self _noteSelectionIsChanging];
4969  [self selectRowIndexes:newSelection byExtendingSelection:shouldExtendSelection];
4970 
4971  _lastSelectedRow = [newSelection containsIndex:aRow] ? aRow : [newSelection lastIndex];
4972 }
4973 
4977 - (void)_noteSelectionIsChanging
4978 {
4980  postNotificationName:CPTableViewSelectionIsChangingNotification
4981  object:self
4982  userInfo:nil];
4983 
4984  if (_implementedDelegateMethods & CPTableViewDelegate_tableViewSelectionIsChanging_)
4985  [_delegate tableViewSelectionIsChanging:[[CPNotification alloc] initWithName:CPTableViewSelectionIsChangingNotification object:self userInfo:nil]];
4986 }
4987 
4991 - (void)_noteSelectionDidChange
4992 {
4994  postNotificationName:CPTableViewSelectionDidChangeNotification
4995  object:self
4996  userInfo:nil];
4997 
4998  if (_implementedDelegateMethods & CPTableViewDelegate_tableViewSelectionDidChange_)
4999  [_delegate tableViewSelectionDidChange:[[CPNotification alloc] initWithName:CPTableViewSelectionDidChangeNotification object:self userInfo:nil]];
5000 }
5001 
5005 - (void)becomeKeyWindow
5006 {
5007  [self setNeedsDisplay:YES];
5008 }
5009 
5013 - (void)resignKeyWindow
5014 {
5015  [self setNeedsDisplay:YES];
5016 }
5017 
5021 - (BOOL)acceptsFirstResponder
5022 {
5023  return YES;
5024 }
5025 
5029 - (BOOL)needsPanelToBecomeKey
5030 {
5031  return YES;
5032 }
5033 
5037 - (CPView)hitTest:(CGPoint)aPoint
5038 {
5039  var hit = [super hitTest:aPoint];
5040 
5041  if ([[CPApp currentEvent] type] !== CPLeftMouseDown)
5042  return hit;
5043 
5044  return [self _hitTest:hit];
5045 }
5046 
5047 - (id)_hitTest:(CPView)aView
5048 {
5049  var column,
5050  row;
5051 
5052  if ([aView acceptsFirstResponder])
5053  {
5054  [self getColumn:@ref(column) row:@ref(row) forView:aView];
5055 
5056  if (![self isRowSelected:row])
5057  {
5058  if (_selectionHighlightStyle == CPTableViewSelectionHighlightStyleNone)
5059  return aView;
5060 
5061  return self;
5062  }
5063  }
5064  else if (!_isViewBased && [aView isKindOfClass:[CPControl class]] && ![aView isKindOfClass:[CPTextField class]])
5065  {
5066  [self getColumn:@ref(column) row:@ref(row) forView:aView];
5067 
5068  _editingColumn = column;
5069  _editingRow = row;
5070 
5071  [aView addObserver:self forKeyPath:@"objectValue" options:CPKeyValueObservingOptionOld|CPKeyValueObservingOptionNew context:"editing"];
5072  }
5073 
5074  return aView;
5075 }
5076 
5077 - (void)_removeObservers
5078 {
5079  if (!_isObserving)
5080  return;
5081 
5082  [self _stopObservingClipView];
5083  [super _removeObservers];
5084 }
5085 
5086 - (void)_addObservers
5087 {
5088  if (_isObserving)
5089  return;
5090 
5091  [self _startObservingClipView];
5092  [super _addObservers];
5093 }
5094 
5099 - (void)viewWillMoveToWindow:(CPWindow)aWindow
5100 {
5101  [super viewWillMoveToWindow:aWindow];
5102 
5103  [self _stopObservingFirstResponderForWindow:aWindow];
5104 
5105  if (aWindow)
5106  [self _startObservingFirstResponderForWindow:aWindow];
5107 }
5108 
5109 - (void)_startObservingClipView
5110 {
5111  if (!_observedClipView)
5112  return;
5113 
5114  var defaultCenter = [CPNotificationCenter defaultCenter];
5115 
5116  [_observedClipView setPostsFrameChangedNotifications:YES];
5117  [_observedClipView setPostsBoundsChangedNotifications:YES];
5118 
5119  [defaultCenter addObserver:self
5120  selector:@selector(superviewFrameChanged:)
5121  name:CPViewFrameDidChangeNotification
5122  object:_observedClipView];
5123 
5124  [defaultCenter addObserver:self
5125  selector:@selector(superviewBoundsChanged:)
5126  name:CPViewBoundsDidChangeNotification
5127  object:_observedClipView];
5128 }
5129 
5130 - (void)_stopObservingClipView
5131 {
5132  if (!_observedClipView)
5133  return;
5134 
5135  var defaultCenter = [CPNotificationCenter defaultCenter];
5136 
5137  [defaultCenter removeObserver:self
5138  name:CPViewFrameDidChangeNotification
5139  object:_observedClipView];
5140 
5141  [defaultCenter removeObserver:self
5142  name:CPViewBoundsDidChangeNotification
5143  object:_observedClipView];
5144 }
5145 
5146 - (void)_startObservingFirstResponderForWindow:(CPWindow)aWindow
5147 {
5148  [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_firstResponderDidChange:) name:_CPWindowDidChangeFirstResponderNotification object:aWindow];
5149 }
5150 
5151 - (void)_stopObservingFirstResponderForWindow:(CPWindow)aWindow
5152 {
5153  [[CPNotificationCenter defaultCenter] removeObserver:self name:_CPWindowDidChangeFirstResponderNotification object:aWindow];
5154 }
5155 
5156 - (void)_firstResponderDidChange:(CPNotification)aNotification
5157 {
5158  var responder = [[self window] firstResponder],
5159  column,
5160  row;
5161 
5162  [self getColumn:@ref(column) row:@ref(row) forView:responder];
5163 
5164  _editingRow = row;
5165  _editingColumn = column;
5166 
5167  // We want to keep the 'First Responder' theme state for the table view as a whole, even when a subview is being edited.
5168  // This makes sure the theming effects of a focused table remain in effect even as cells are being edited in it.
5169  // Check if the firstResponder is outside the tableview:
5170  if (responder !== self && _editingRow == CPNotFound && _editingColumn == CPNotFound)
5171  [self _notifyViewDidResignFirstResponder];
5172  else
5173  [self _notifyViewDidBecomeFirstResponder];
5174 
5175  // This is for cell-based tables only. In view-based mode, we do not change the textfield apprearence during an edit.
5176  if (!_isViewBased && _editingRow !== CPNotFound && [responder isKindOfClass:[CPTextField class]] && [responder isEditable])
5177  {
5178  [responder setBezeled:YES];
5179  [self _registerForEndEditingNote:responder];
5180  }
5181 }
5182 
5183 - (void)_registerForEndEditingNote:(CPView)aTextField
5184 {
5185  [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_textFieldEditingDidEnd:) name:CPControlTextDidEndEditingNotification object:aTextField];
5186 }
5187 
5188 - (void)_unregisterForEndEditingNote:(CPView)aTextField
5189 {
5190  [[CPNotificationCenter defaultCenter] removeObserver:self name:CPControlTextDidEndEditingNotification object:aTextField];
5191 }
5192 
5193 - (void)_textFieldEditingDidEnd:(CPNotification)aNote
5194 {
5195  // FIXME: When you edit a text field and hit enter without any text modification, the CPControlTextDidEndEditingNotification
5196  // is NOT sent. This is a bug in CPTextField or CPControl according to cocoa.
5197  var textField = [aNote object];
5198 
5199  [self _unregisterForEndEditingNote:textField];
5200 
5201  if (!_isViewBased)
5202  {
5203  [self _setEditingState:NO forView:textField];
5204  [self _commitDataViewObjectValue:textField];
5205  }
5206  else
5207  [textField setBezeled:NO];
5208 
5209  [self _resignFirstResponderWithoutSendingAction:textField];
5210 }
5211 
5212 - (void)_resignFirstResponderWithoutSendingAction:(CPView)aView
5213 {
5214  var action = [self _disableActionIfExists:aView];
5215 
5216  [[self window] makeFirstResponder:self];
5217 
5218  if (action)
5219  [aView setAction:action];
5220 }
5221 
5222 - (void)_resignEditedView
5223 {
5224  var view = [[self window] firstResponder];
5225 
5226  if ([view respondsToSelector:@selector(selectText:)])
5227  [view selectText:nil];
5228 
5229  if (!_isViewBased)
5230  {
5231  [self _unregisterForEndEditingNote:view];
5232  [self _setEditingState:NO forView:view];
5233  }
5234 
5235  [self _resignFirstResponderWithoutSendingAction:view];
5236 }
5237 
5238 - (SEL)_disableActionIfExists:(CPView)aView
5239 {
5240  // TODO: We disable action to prevent it from beeing sent twice when we resign the FR inside a textEndEditing notification.
5241  // Check if this is due to a bug in CPTextField.
5242  var action = nil;
5243  if ([aView respondsToSelector:@selector(action)] && (action = [aView action]))
5244  [aView setAction:nil];
5245 
5246  return action;
5247 }
5248 
5254 - (void)_commitDataViewObjectValue:(id)aDataView
5255 {
5256  var editingTableColumn = _tableColumns[_editingColumn];
5257 
5258  if (_implementedDataSourceMethods & CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_)
5259  [_dataSource tableView:self setObjectValue:[aDataView objectValue] forTableColumn:editingTableColumn row:_editingRow];
5260 
5261  // Allow the column binding to do a reverse set. Note that we do this even if the data source method above
5262  // is implemented.
5263  [editingTableColumn _reverseSetDataView:aDataView forRow:_editingRow];
5264 
5265  if (_editingRow !== CPNotFound && _editingColumn !== CPNotFound)
5266  [self reloadDataForRowIndexes:[CPIndexSet indexSetWithIndex:_editingRow] columnIndexes:[CPIndexSet indexSetWithIndex:_editingColumn]];
5267 }
5268 
5269 - (void)_setEditingState:(BOOL)editingState forView:(CPView)aView
5270 {
5271  if ([aView respondsToSelector:@selector(setEditable:)])
5272  [aView setEditable:editingState];
5273 
5274  if ([aView respondsToSelector:@selector(setSelectable:)])
5275  [aView setSelectable:editingState];
5276 
5277  if ([aView isKindOfClass:[CPTextField class]])
5278  [aView setBezeled:editingState];
5279 }
5289 - (void)editColumn:(CPInteger)columnIndex row:(CPInteger)rowIndex withEvent:(CPEvent)theEvent select:(BOOL)flag
5290 {
5291  if (![self isRowSelected:rowIndex])
5292  [[CPException exceptionWithName:@"Error" reason:@"Attempt to edit row " + rowIndex + " when not selected." userInfo:nil] raise];
5293 
5294  [self reloadData];
5295 
5296  // Process all events immediately to make sure table data views are reloaded.
5297  [[CPRunLoop currentRunLoop] limitDateForMode:CPDefaultRunLoopMode];
5298 
5299  [self scrollRowToVisible:rowIndex];
5300  [self scrollColumnToVisible:columnIndex];
5301 
5302  // TODO Do something with flag.
5303 
5304  _editingRow = rowIndex;
5305  _editingColumn = columnIndex;
5306 
5307  var editingTableColumnUID = [_tableColumns[_editingColumn] UID],
5308  editingView = _dataViewsForRows[_editingRow][editingTableColumnUID];
5309 
5310  [self _setEditingState:YES forView:editingView];
5311  [[self window] makeFirstResponder:editingView];
5312 }
5313 
5314 - (void)observeValueForKeyPath:(CPString)keyPath ofObject:(id)object change:(CPDictionary)change context:(void)context
5315 {
5316  if (context === "editing" && [object superview] === self)
5317  {
5318  [object removeObserver:self forKeyPath:keyPath];
5319  [self _commitDataViewObjectValue:object];
5320  _editingRow = CPNotFound;
5321  _editingColumn = CPNotFound;
5322  }
5323 }
5324 
5328 - (CPInteger)editedColumn
5329 {
5330  return _editingColumn;
5331 }
5332 
5336 - (CPInteger)editedRow
5337 {
5338  return _editingRow;
5339 }
5340 
5344 - (void)keyDown:(CPEvent)anEvent
5345 {
5346  var character = [anEvent charactersIgnoringModifiers],
5347  modifierFlags = [anEvent modifierFlags];
5348 
5349  // Check for the key events manually, as opposed to waiting for CPWindow to sent the actual action message
5350  // in _processKeyboardUIKey:, because we might not want to handle the arrow events.
5351  if (character === CPUpArrowFunctionKey || character === CPDownArrowFunctionKey)
5352  {
5353  // We're not interested in the arrow keys if there are no rows.
5354  // Technically we should also not be interested if we can't scroll,
5355  // but Cocoa doesn't handle that situation either.
5356  if ([self numberOfRows] !== 0)
5357  {
5358  [self _moveSelectionWithEvent:anEvent upward:(character === CPUpArrowFunctionKey)];
5359 
5360  return;
5361  }
5362  }
5363  else if (character === CPDeleteCharacter || character === CPDeleteFunctionKey)
5364  {
5365  // Don't call super if the delegate is interested in the delete key
5366  if ([self _sendDelegateDeleteKeyPressed])
5367  return;
5368  }
5369 
5370  [super keyDown:anEvent];
5371 }
5372 
5378 - (BOOL)_selectionIsBroken
5379 {
5380  return [self selectedRowIndexes]._ranges.length !== 1;
5381 }
5382 
5388 - (void)_moveSelectionWithEvent:(CPEvent)theEvent upward:(BOOL)shouldGoUpward
5389 {
5390  if (![self _sendDelegateSelectionShouldChangeInTableView])
5391  return;
5392 
5393  var selectedIndexes = [self selectedRowIndexes];
5394 
5395  if ([selectedIndexes count] > 0)
5396  {
5397  var extend = (([theEvent modifierFlags] & CPShiftKeyMask) && _allowsMultipleSelection),
5398  i = [self selectedRow];
5399 
5400  if ([self _selectionIsBroken])
5401  {
5402  while ([selectedIndexes containsIndex:i])
5403  {
5404  shouldGoUpward ? i-- : i++;
5405  }
5406  _wasSelectionBroken = true;
5407  }
5408  else if (_wasSelectionBroken && ((shouldGoUpward && i !== [selectedIndexes firstIndex]) || (!shouldGoUpward && i !== [selectedIndexes lastIndex])))
5409  {
5410  shouldGoUpward ? i = [selectedIndexes firstIndex] - 1 : i = [selectedIndexes lastIndex];
5411  _wasSelectionBroken = false;
5412  }
5413  else
5414  {
5415  shouldGoUpward ? i-- : i++;
5416  }
5417  }
5418  else
5419  {
5420  var extend = NO;
5421  //no rows are currently selected
5422  if ([self numberOfRows] > 0)
5423  var i = shouldGoUpward ? [self numberOfRows] - 1 : 0; // if we select upward select the last row, otherwise select the first row
5424  }
5425 
5426  if (i >= [self numberOfRows] || i < 0)
5427  return;
5428 
5429  if ([self _delegateRespondsToSelectionIndexesForProposedSelection] || [self _delegateRespondsToShouldSelectRow])
5430  {
5431  var shouldSelect = !![[self _cleanUpSelectionRowIndexes:[CPIndexSet indexSetWithIndex:i]] count];
5432 
5433  /* If shouldSelect returns NO it means this row cannot be selected.
5434  The proper behaviour is to then try to see if the next/previous
5435  row(s) can be selected, until we hit the first one that can be.
5436  */
5437  while (!shouldSelect && (i < [self numberOfRows] && i > 0))
5438  {
5439  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.
5440  shouldSelect = !![[self _cleanUpSelectionRowIndexes:[CPIndexSet indexSetWithIndex:i]] count];
5441  }
5442 
5443  if (!shouldSelect)
5444  return;
5445  }
5446 
5447  // If we go upward and see that this row is already selected we should deselect the row below.
5448  if (extend && [selectedIndexes containsIndex:i])
5449  {
5450  // The row we're on is the last to be selected.
5451  var differedLastSelectedRow = i;
5452 
5453  // no remove the one before/after it
5454  shouldGoUpward ? i++ : i--;
5455 
5456  [selectedIndexes removeIndex:i];
5457 
5458  //we're going to replace the selection
5459  extend = NO;
5460  }
5461  else if (extend)
5462  {
5463  if ([selectedIndexes containsIndex:i])
5464  {
5465  i = shouldGoUpward ? [selectedIndexes firstIndex] -1 : [selectedIndexes lastIndex] + 1;
5466  i = MIN(MAX(i, 0), [self numberOfRows] - 1);
5467  }
5468 
5469  [selectedIndexes addIndex:i];
5470  var differedLastSelectedRow = i;
5471  }
5472  else
5473  {
5474  selectedIndexes = [CPIndexSet indexSetWithIndex:i];
5475  var differedLastSelectedRow = i;
5476  }
5477 
5478  selectedIndexes = [self _sendDelegateSelectionIndexesForProposedSelection:selectedIndexes];
5479 
5480  [self selectRowIndexes:selectedIndexes byExtendingSelection:extend];
5481 
5482  // we differ because selectRowIndexes: does its own thing which would set the wrong index
5483  _lastSelectedRow = differedLastSelectedRow;
5484 
5485  if (i !== CPNotFound)
5486  [self scrollRowToVisible:i];
5487 }
5488 
5489 @end
5490 
5491 
5493 
5498 - (BOOL)_dataSourceRespondsToObjectValueForTableColumn
5499 {
5500  return _implementedDataSourceMethods & CPTableViewDataSource_tableView_objectValueForTableColumn_row_;
5501 }
5502 
5507 - (BOOL)_dataSourceRespondsToWriteRowsWithIndexesToPasteboard
5508 {
5509  return _implementedDataSourceMethods & CPTableViewDataSource_tableView_writeRowsWithIndexes_toPasteboard_;
5510 }
5511 
5516 - (BOOL)_dataSourceRespondsToSetObjectValueForTableColumnRow
5517 {
5519 }
5520 
5525 - (BOOL)_dataSourceRespondsToValidateDropProposedRowProposedDropOperation
5526 {
5528 }
5529 
5534 - (BOOL)_dataSourceRespondsToNumberOfRowsinTableView
5535 {
5536  return _implementedDataSourceMethods & CPTableViewDataSource_numberOfRowsInTableView_;
5537 }
5538 
5544 - (int)_sendDataSourceNumberOfRowsInTableView
5545 {
5546  if (!(_implementedDataSourceMethods & CPTableViewDataSource_numberOfRowsInTableView_))
5547  return 0;
5548 
5549  return [_dataSource numberOfRowsInTableView:self];
5550 }
5551 
5557 - (id)_sendDataSourceObjectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5558 {
5559  if (!(_implementedDataSourceMethods & CPTableViewDataSource_tableView_objectValueForTableColumn_row_))
5560  return nil;
5561 
5562  return [_dataSource tableView:self objectValueForTableColumn:aTableColumn row:aRowIndex];
5563 }
5564 
5569 - (void)_sendDataSourceSetObjectValue:(id)anObject forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5570 {
5571  if (!(_implementedDataSourceMethods & CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_))
5572  return;
5573 
5574  [_dataSource tableView:self setObjectValue:anObject forTableColumn:aTableColumn row:aRowIndex];
5575 }
5576 
5581 - (void)_sendDataSourceSortDescriptorsDidChange:(CPArray)descriptors
5582 {
5583  if (!(_implementedDataSourceMethods & CPTableViewDataSource_tableView_sortDescriptorsDidChange_))
5584  return;
5585 
5586  [_dataSource tableView:self sortDescriptorsDidChange:descriptors];
5587 }
5588 
5593 - (BOOL)_sendDataSourceAcceptDrop:(id)info row:(CPInteger)aRowIndex dropOperation:(CPTableViewDropOperation)operation
5594 {
5595  if (!(_implementedDataSourceMethods & CPTableViewDataSource_tableView_acceptDrop_row_dropOperation_))
5596  return NO;
5597 
5598  return [_dataSource tableView:self acceptDrop:info row:aRowIndex dropOperation:operation];
5599 }
5600 
5605 - (CPDragOperation)_sendDataSourceValidateDrop:(id)info proposedRow:(CPInteger)aRowIndex proposedDropOperation:(CPTableViewDropOperation)operation
5606 {
5608  return CPDragOperationNone;
5609 
5610  return [_dataSource tableView:self validateDrop:info proposedRow:aRowIndex proposedDropOperation:operation];
5611 }
5612 
5617 - (BOOL)_sendDataSourceWriteRowsWithIndexes:(CPIndexSet)rowIndexes toPasteboard:(CPPasteboard)pboard
5618 {
5619  if (!(_implementedDataSourceMethods & CPTableViewDataSource_tableView_writeRowsWithIndexes_toPasteboard_))
5620  return NO;
5621 
5622  return [_dataSource tableView:self writeRowsWithIndexes:rowIndexes toPasteboard:pboard];
5623 }
5624 
5625 /*
5626  This method is sent to the data source for convenience...
5627 */
5628 - (void)draggedImage:(CPImage)anImage endedAt:(CGPoint)aLocation operation:(CPDragOperation)anOperation
5629 {
5630  if ([_dataSource respondsToSelector:@selector(tableView:didEndDraggedImage:atPosition:operation:)])
5631  [_dataSource tableView:self didEndDraggedImage:anImage atPosition:aLocation operation:anOperation];
5632 }
5633 
5634 
5635 #pragma mark -
5636 #pragma mark DataSource methods to implement
5637 
5642 - (CPArray)_sendDataSourceNamesOfPromisedFilesDroppedAtDestination:(CPURL)dropDestination forDraggedRowsWithIndexes:(CPIndexSet)indexSet
5643 {
5645  return [];
5646 
5647  return [_dataSource tableView:self namesOfPromisedFilesDroppedAtDestination:dropDestination forDraggedRowsWithIndexes:indexSet];
5648 }
5649 
5650 @end
5651 
5652 
5654 
5659 - (BOOL)_delegateRespondsToDataViewForTableColumn
5660 {
5661  return _implementedDelegateMethods & CPTableViewDelegate_tableView_dataViewForTableColumn_row_;
5662 }
5663 
5668 - (BOOL)_delegateRespondsToViewForTableColumn
5669 {
5670  return _implementedDelegateMethods & CPTableViewDelegate_tableView_viewForTableColumn_row_;
5671 }
5672 
5677 - (BOOL)_delegateRespondsToShouldSelectRow
5678 {
5679  return _implementedDelegateMethods & CPTableViewDelegate_tableView_shouldSelectRow_;
5680 }
5681 
5686 - (BOOL)_delegateRespondsToSelectionShouldChangeInTableView
5687 {
5688  return _implementedDelegateMethods & CPTableViewDelegate_selectionShouldChangeInTableView_;
5689 }
5690 
5695 - (BOOL)_delegateRespondsToSelectionIndexesForProposedSelection
5696 {
5697  return _implementedDelegateMethods & CPTableViewDelegate_tableView_selectionIndexesForProposedSelection_;
5698 }
5699 
5704 - (BOOL)_delegateRespondsToMenuForTableColumnRow
5705 {
5706  return _implementedDelegateMethods & CPTableViewDelegate_tableViewMenuForTableColumn_row_;
5707 }
5708 
5713 - (void)_sendDelegateDidClickTableColumn:(CPInteger)column
5714 {
5715  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_didClickTableColumn_)
5716  [_delegate tableView:self didClickTableColumn:_tableColumns[column]];
5717 }
5718 
5723 - (void)_sendDelegateDidDragTableColumn:(CPInteger)column
5724 {
5725  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_didDragTableColumn_)
5726  [_delegate tableView:self didDragTableColumn:_tableColumns[column]];
5727 }
5728 
5733 - (void)_sendDelegateMouseDownInHeaderOfTableColumn:(CPInteger)column
5734 {
5735  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_mouseDownInHeaderOfTableColumn_)
5736  [_delegate tableView:self mouseDownInHeaderOfTableColumn:_tableColumns[column]];
5737 }
5738 
5739 /*
5740  @ignore
5741  Call the delegate tableViewDeleteKeyPressed
5742 */
5743 - (BOOL)_sendDelegateDeleteKeyPressed
5744 {
5745  if ([_delegate respondsToSelector: @selector(tableViewDeleteKeyPressed:)])
5746  {
5747  [_delegate tableViewDeleteKeyPressed:self];
5748  return YES;
5749  }
5750 
5751  return NO;
5752 }
5753 
5758 - (BOOL)_sendDelegateSelectionShouldChangeInTableView
5759 {
5760  if (!(_implementedDelegateMethods & CPTableViewDelegate_selectionShouldChangeInTableView_))
5761  return YES;
5762 
5763  return [_delegate selectionShouldChangeInTableView:self];
5764 }
5765 
5770 - (BOOL)_sendDelegateIsGroupRow:(CPInteger)anIndex
5771 {
5772  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_isGroupRow_))
5773  return NO;
5774 
5775  return [_delegate tableView:self isGroupRow:anIndex];
5776 }
5777 
5782 - (BOOL)_sendDelegateShouldSelectRow:(CPInteger)anIndex
5783 {
5784  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldSelectRow_))
5785  return YES;
5786 
5787  return [_delegate tableView:self shouldSelectRow:anIndex];
5788 }
5789 
5794 - (void)_sendDelegateWillDisplayView:(id)aCell forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5795 {
5796  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_willDisplayView_forTableColumn_row_))
5797  return;
5798 
5799  [_delegate tableView:self willDisplayView:aCell forTableColumn:aTableColumn row:aRowIndex];
5800 }
5801 
5806 - (void)_sendDelegateWillRemoveView:(id)aCell forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5807 {
5808  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_willRemoveView_forTableColumn_row_))
5809  return;
5810 
5811  [_delegate tableView:self willRemoveView:aCell forTableColumn:aTableColumn row:aRowIndex];
5812 }
5813 
5818 - (CPMenu)_sendDelegateMenuForTableColumn:(CPTableColumn)aTableColumn row:aRowIndex
5819 {
5820  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableViewMenuForTableColumn_row_))
5821  return nil;
5822 
5823  return [_delegate tableView:self menuForTableColumn:aTableColumn row:aRowIndex];
5824 }
5825 
5826 /*
5827  @ignore
5828  Returns YES if the column at columnIndex can be reordered.
5829  It can be possible if column reordering is allowed and if the tableview
5830  delegate also accept the reordering
5831 */
5832 - (BOOL)_sendDelegateShouldReorderColumn:(CPInteger)columnIndex toColumn:(CPInteger)newColumnIndex
5833 {
5834  if ([self allowsColumnReordering] &&
5836  {
5837  return [_delegate tableView:self shouldReorderColumn:columnIndex toColumn:newColumnIndex];
5838  }
5839 
5840  return [self allowsColumnReordering];
5841 }
5842 
5847 - (float)_sendDelegateHeightOfRow:(CPInteger)anIndex
5848 {
5849  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_heightOfRow_))
5850  return [self rowHeight];
5851 
5852  return [_delegate tableView:self heightOfRow:anIndex];
5853 }
5854 
5859 - (BOOL)_sendDelegateShouldEditTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5860 {
5861  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldEditTableColumn_row_))
5862  return YES;
5863 
5864  return [_delegate tableView:self shouldEditTableColumn:aTableColumn row:aRowIndex];
5865 }
5866 
5871 - (CPIndexSet)_sendDelegateSelectionIndexesForProposedSelection:(CPIndexSet)anIndexSet
5872 {
5873  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_selectionIndexesForProposedSelection_))
5874  return anIndexSet;
5875 
5876  return [_delegate tableView:self selectionIndexesForProposedSelection:anIndexSet];
5877 }
5878 
5883 - (CPView)_sendDelegateViewForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5884 {
5885  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_viewForTableColumn_row_))
5886  return nil;
5887 
5888  return [_delegate tableView:self viewForTableColumn:aTableColumn row:aRowIndex];
5889 }
5890 
5895 - (CPView)_sendDelegateDataViewForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5896 {
5897  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_dataViewForTableColumn_row_))
5898  return nil;
5899 
5900  return [_delegate tableView:self dataViewForTableColumn:aTableColumn row:aRowIndex];
5901 }
5902 
5903 
5904 #pragma mark -
5905 #pragma mark Delegate methods to implement
5906 
5911 - (BOOL)_sendDelegateShouldSelectTableColumn:(CPTableColumn)aTableColumn
5912 {
5913  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldSelectTableColumn_))
5914  return YES;
5915 
5916  return [_delegate tableView:self shouldSelectTableColumn:aTableColumn];
5917 }
5918 
5923 - (CPString)_sendDelegateToolTipForView:(id)aView rect:(CGRect)aRect tableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex mouseLocation:(CGPoint)aPoint
5924 {
5926  return nil;
5927 
5928  return [_delegate tableView:self toolTipForView:aView rect:aRect tableColumn:aTableColumn row:aRowIndex mouseLocation:aPoint];
5929 }
5930 
5935 - (BOOL)_sendDelegateShouldTrackView:(id)aView forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5936 {
5937  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldTrackView_forTableColumn_row_))
5938  return YES;
5939 
5940  return [_delegate tableView:self shouldTrackView:aView forTableColumn:aTableColumn row:aRowIndex];
5941 }
5942 
5947 - (BOOL)_sendDelegateShouldShowViewExpansionForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5948 {
5950  return YES;
5951 
5952  return [_delegate tableView:self shouldShowViewExpansionForTableColumn:aTableColumn row:aRowIndex];
5953 }
5954 
5959 - (BOOL)_sendDelegateShouldTypeSelectForEvent:(CPEvent)anEvent withCurrentSearchString:(CPString)aString
5960 {
5962  return NO;
5963 
5964  return [_delegate tableView:self shouldTypeSelectForEvent:anEvent withCurrentSearchString:aString];
5965 }
5966 
5971 - (CPString)_sendDelegateTypeSelectStringForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5972 {
5973  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_typeSelectStringForTableColumn_row_))
5974  return nil;
5975 
5976  return [_delegate tableView:self typeSelectStringForTableColumn:aTableColumn row:aRowIndex];
5977 }
5978 
5983 - (int)_sendDelegateNextTypeSelectMatchFromRow:(CPInteger)aRowIndex toRow:(CPInteger)aSecondRowIndex forString:(CPString)aString
5984 {
5986  return -1;
5987 
5988  return [_delegate tableView:self nextTypeSelectMatchFromRow:aRowIndex toRow:aSecondRowIndex forString:aString];
5989 }
5990 
5991 @end
5992 
5993 @implementation CPTableView (Bindings)
5994 
5995 + (Class)_binderClassForBinding:(CPString)aBinding
5996 {
5997  if (aBinding == @"content")
5998  return [CPTableContentBinder class];
5999 
6000  return [super _binderClassForBinding:aBinding];
6001 }
6002 
6006 - (CPString)_replacementKeyPathForBinding:(CPString)aBinding
6007 {
6008  if (aBinding === @"selectionIndexes")
6009  return @"selectedRowIndexes";
6010 
6011  return [super _replacementKeyPathForBinding:aBinding];
6012 }
6013 
6017 - (void)_establishBindingsIfUnbound:(id)destination
6018 {
6019  if ([[self infoForBinding:@"content"] objectForKey:CPObservedObjectKey] !== destination)
6020  {
6021  [super bind:@"content" toObject:destination withKeyPath:@"arrangedObjects" options:nil];
6022  _contentBindingExplicitlySet = NO;
6023  }
6024 
6025  // If the content binding was set manually assume the user is taking manual control of establishing bindings.
6026  if (!_contentBindingExplicitlySet)
6027  {
6028  if ([[self infoForBinding:@"selectionIndexes"] objectForKey:CPObservedObjectKey] !== destination)
6029  [self bind:@"selectionIndexes" toObject:destination withKeyPath:@"selectionIndexes" options:nil];
6030 
6031  if ([[self infoForBinding:@"sortDescriptors"] objectForKey:CPObservedObjectKey] !== destination)
6032  [self bind:@"sortDescriptors" toObject:destination withKeyPath:@"sortDescriptors" options:nil];
6033  }
6034 }
6035 
6036 - (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options
6037 {
6038  if (aBinding == @"content")
6039  _contentBindingExplicitlySet = YES;
6040 
6041  [super bind:aBinding toObject:anObject withKeyPath:aKeyPath options:options];
6042 }
6043 
6044 @end
6045 
6046 
6047 @implementation CPTableContentBinder : CPBinder
6048 {
6049  id _content;
6050 }
6051 
6052 - (void)setValueFor:(CPString)aBinding
6053 {
6054  var destination = [_info objectForKey:CPObservedObjectKey],
6055  keyPath = [_info objectForKey:CPObservedKeyPathKey];
6056 
6057  _content = [destination valueForKey:keyPath];
6058  // FIXME: reload data for all rows or just exposed rows ?
6059  [_source _reloadDataViews];
6060 }
6061 
6062 @end
6063 
6064 
6065 var CPTableViewDataSourceKey = @"CPTableViewDataSourceKey",
6066  CPTableViewDelegateKey = @"CPTableViewDelegateKey",
6067  CPTableViewHeaderViewKey = @"CPTableViewHeaderViewKey",
6068  CPTableViewTableColumnsKey = @"CPTableViewTableColumnsKey",
6069  CPTableViewRowHeightKey = @"CPTableViewRowHeightKey",
6070  CPTableViewIntercellSpacingKey = @"CPTableViewIntercellSpacingKey",
6071  CPTableViewSelectionHighlightStyleKey = @"CPTableViewSelectionHighlightStyleKey",
6072  CPTableViewMultipleSelectionKey = @"CPTableViewMultipleSelectionKey",
6073  CPTableViewEmptySelectionKey = @"CPTableViewEmptySelectionKey",
6074  CPTableViewColumnReorderingKey = @"CPTableViewColumnReorderingKey",
6075  CPTableViewColumnResizingKey = @"CPTableViewColumnResizingKey",
6076  CPTableViewColumnSelectionKey = @"CPTableViewColumnSelectionKey",
6077  CPTableViewColumnAutoresizingStyleKey = @"CPTableViewColumnAutoresizingStyleKey",
6078  CPTableViewGridColorKey = @"CPTableViewGridColorKey",
6079  CPTableViewGridStyleMaskKey = @"CPTableViewGridStyleMaskKey",
6080  CPTableViewUsesAlternatingBackgroundKey = @"CPTableViewUsesAlternatingBackgroundKey",
6081  CPTableViewAlternatingRowColorsKey = @"CPTableViewAlternatingRowColorsKey",
6082  CPTableViewHeaderViewKey = @"CPTableViewHeaderViewKey",
6083  CPTableViewCornerViewKey = @"CPTableViewCornerViewKey",
6084  CPTableViewAutosaveNameKey = @"CPTableViewAutosaveNameKey",
6085  CPTableViewArchivedReusableViewsKey = @"CPTableViewArchivedReusableViewsKey";
6086 
6087 @implementation CPTableView (CPCoding)
6088 
6089 - (id)initWithCoder:(CPCoder)aCoder
6090 {
6091  self = [super initWithCoder:aCoder];
6092 
6093  if (self)
6094  {
6095  //Configuring Behavior
6096  _allowsColumnReordering = [aCoder decodeBoolForKey:CPTableViewColumnReorderingKey];
6097  _allowsColumnResizing = [aCoder decodeBoolForKey:CPTableViewColumnResizingKey];
6098  _allowsMultipleSelection = [aCoder decodeBoolForKey:CPTableViewMultipleSelectionKey];
6099  _allowsEmptySelection = [aCoder decodeBoolForKey:CPTableViewEmptySelectionKey];
6100  _allowsColumnSelection = [aCoder decodeBoolForKey:CPTableViewColumnSelectionKey];
6101 
6102  //Setting Display Attributes
6103  _selectionHighlightStyle = [aCoder decodeIntForKey:CPTableViewSelectionHighlightStyleKey];
6104  _columnAutoResizingStyle = [aCoder decodeIntForKey:CPTableViewColumnAutoresizingStyleKey];
6105 
6106  _tableColumns = [aCoder decodeObjectForKey:CPTableViewTableColumnsKey] || [];
6107  [_tableColumns makeObjectsPerformSelector:@selector(setTableView:) withObject:self];
6108 
6109  _rowHeight = [aCoder decodeFloatForKey:CPTableViewRowHeightKey] || [self valueForThemeAttribute:@"default-row-height"];
6110  _intercellSpacing = [aCoder decodeSizeForKey:CPTableViewIntercellSpacingKey];
6111 
6112  if (CGSizeEqualToSize(_intercellSpacing, CGSizeMakeZero()))
6113  _intercellSpacing = CGSizeMake(3.0, 2.0);
6114 
6115  [self setGridColor:[aCoder decodeObjectForKey:CPTableViewGridColorKey]];
6116  _gridStyleMask = [aCoder decodeIntForKey:CPTableViewGridStyleMaskKey];
6117 
6118  _usesAlternatingRowBackgroundColors = [aCoder decodeObjectForKey:CPTableViewUsesAlternatingBackgroundKey];
6119  [self setAlternatingRowBackgroundColors:[aCoder decodeObjectForKey:CPTableViewAlternatingRowColorsKey]];
6120 
6121  _headerView = [aCoder decodeObjectForKey:CPTableViewHeaderViewKey];
6122  _cornerView = [aCoder decodeObjectForKey:CPTableViewCornerViewKey];
6123 
6124  [self setDataSource:[aCoder decodeObjectForKey:CPTableViewDataSourceKey]];
6125  [self setDelegate:[aCoder decodeObjectForKey:CPTableViewDelegateKey]];
6126 
6127  [self _init];
6128 
6129  if ([aCoder containsValueForKey:CPTableViewArchivedReusableViewsKey])
6130  _archivedDataViews = [aCoder decodeObjectForKey:CPTableViewArchivedReusableViewsKey];
6131 
6132  [self _updateIsViewBased];
6133 
6134  [self viewWillMoveToSuperview:[self superview]];
6135 
6136  // Do this as late as possible to make sure the tableview is fully configured
6137  [self setAutosaveName:[aCoder decodeObjectForKey:CPTableViewAutosaveNameKey]];
6138  }
6139 
6140  return self;
6141 }
6142 
6143 - (void)encodeWithCoder:(CPCoder)aCoder
6144 {
6145  [super encodeWithCoder:aCoder];
6146 
6147  // We do this in order to avoid encoding the _tableDrawView, which
6148  // should just automatically be created programmatically as needed.
6149  if (_tableDrawView)
6150  [_tableDrawView removeFromSuperview];
6151 
6152  [aCoder encodeObject:_dataSource forKey:CPTableViewDataSourceKey];
6153  [aCoder encodeObject:_delegate forKey:CPTableViewDelegateKey];
6154 
6155  [aCoder encodeFloat:_rowHeight forKey:CPTableViewRowHeightKey];
6156  [aCoder encodeSize:_intercellSpacing forKey:CPTableViewIntercellSpacingKey];
6157 
6158  [aCoder encodeInt:_selectionHighlightStyle forKey:CPTableViewSelectionHighlightStyleKey];
6159  [aCoder encodeInt:_columnAutoResizingStyle forKey:CPTableViewColumnAutoresizingStyleKey];
6160 
6161  [aCoder encodeBool:_allowsMultipleSelection forKey:CPTableViewMultipleSelectionKey];
6162  [aCoder encodeBool:_allowsEmptySelection forKey:CPTableViewEmptySelectionKey];
6163  [aCoder encodeBool:_allowsColumnReordering forKey:CPTableViewColumnReorderingKey];
6164  [aCoder encodeBool:_allowsColumnResizing forKey:CPTableViewColumnResizingKey];
6165  [aCoder encodeBool:_allowsColumnSelection forKey:CPTableViewColumnSelectionKey];
6166 
6167  [aCoder encodeObject:_tableColumns forKey:CPTableViewTableColumnsKey];
6168 
6169  [aCoder encodeObject:[self gridColor] forKey:CPTableViewGridColorKey];
6170  [aCoder encodeInt:_gridStyleMask forKey:CPTableViewGridStyleMaskKey];
6171 
6172  [aCoder encodeBool:_usesAlternatingRowBackgroundColors forKey:CPTableViewUsesAlternatingBackgroundKey];
6173  [aCoder encodeObject:[self alternatingRowBackgroundColors] forKey:CPTableViewAlternatingRowColorsKey];
6174 
6175  [aCoder encodeObject:_cornerView forKey:CPTableViewCornerViewKey];
6176  [aCoder encodeObject:_headerView forKey:CPTableViewHeaderViewKey];
6177 
6178  [aCoder encodeObject:_autosaveName forKey:CPTableViewAutosaveNameKey];
6179 
6180  if (_archivedDataViews)
6181  [aCoder encodeObject:_archivedDataViews forKey:CPTableViewArchivedReusableViewsKey];
6182 }
6183 
6184 @end
6185 
6186 @implementation _CPDropOperationDrawingView : CPView
6187 {
6188  unsigned dropOperation;
6192 }
6193 
6194 - (void)drawRect:(CGRect)aRect
6195 {
6196  if (tableView._destinationDragStyle === CPTableViewDraggingDestinationFeedbackStyleNone || isBlinking)
6197  return;
6198 
6199  var context = [[CPGraphicsContext currentContext] graphicsPort],
6200  borderRadius,
6201  borderColor,
6202  borderWidth,
6204 
6205  if (currentRow === -1)
6206  {
6207  borderColor = [tableView valueForThemeAttribute:@"dropview-on-border-color"];
6208  borderWidth = [tableView valueForThemeAttribute:@"dropview-on-border-width"];
6209 
6210  CGContextSetStrokeColor(context, borderColor);
6211  CGContextSetLineWidth(context, borderWidth);
6212  CGContextStrokeRect(context, [self bounds]);
6213  }
6214 
6215  else if (dropOperation === CPTableViewDropOn)
6216  {
6217  //if row is selected don't fill and stroke white
6218  var selectedRows = [tableView selectedRowIndexes],
6219  newRect = CGRectMake(aRect.origin.x + 2, aRect.origin.y + 2, aRect.size.width - 4, aRect.size.height - 5);
6220 
6221  if ([selectedRows containsIndex:currentRow])
6222  {
6223  borderRadius = [tableView valueForThemeAttribute:@"dropview-on-selected-border-radius"];
6224  borderColor = [tableView valueForThemeAttribute:@"dropview-on-selected-border-color"];
6225  borderWidth = [tableView valueForThemeAttribute:@"dropview-on-selected-border-width"];
6226  backgroundColor = [tableView valueForThemeAttribute:@"dropview-on-selected-background-color"];
6227  }
6228  else
6229  {
6230  borderRadius = [tableView valueForThemeAttribute:@"dropview-on-border-radius"];
6231  borderColor = [tableView valueForThemeAttribute:@"dropview-on-border-color"];
6232  borderWidth = [tableView valueForThemeAttribute:@"dropview-on-border-width"];
6233  backgroundColor = [tableView valueForThemeAttribute:@"dropview-on-background-color"];
6234  }
6235 
6236  CGContextSetStrokeColor(context, borderColor);
6237  CGContextSetLineWidth(context, borderWidth);
6239  CGContextFillRoundedRectangleInRect(context, newRect, borderRadius, YES, YES, YES, YES);
6240  CGContextStrokeRoundedRectangleInRect(context, newRect, borderRadius, YES, YES, YES, YES);
6241 
6242  }
6243  else if (dropOperation === CPTableViewDropAbove)
6244  {
6245  //reposition the view up a tad
6246  [self setFrameOrigin:CGPointMake(_frame.origin.x, _frame.origin.y - 8)];
6247 
6248  var selectedRows = [tableView selectedRowIndexes];
6249 
6250  if ([selectedRows containsIndex:currentRow - 1] || [selectedRows containsIndex:currentRow])
6251  {
6252  borderColor = [tableView valueForThemeAttribute:@"dropview-above-selected-border-color"];
6253  borderWidth = [tableView valueForThemeAttribute:@"dropview-above-selected-border-width"];
6254  }
6255  else
6256  {
6257  borderColor = [tableView valueForThemeAttribute:@"dropview-above-border-color"];
6258  borderWidth = [tableView valueForThemeAttribute:@"dropview-above-border-width"];
6259  }
6260 
6261  CGContextSetStrokeColor(context, borderColor);
6262  CGContextSetLineWidth(context, borderWidth);
6263  CGContextStrokeEllipseInRect(context, CGRectMake(aRect.origin.x + 4, aRect.origin.y + 4, 8, 8)); // circle
6264 
6265  CGContextBeginPath(context);
6266  CGContextMoveToPoint(context, 10, aRect.origin.y + 8);
6267  CGContextAddLineToPoint(context, aRect.size.width - aRect.origin.y - 8, aRect.origin.y + 8);
6268  CGContextStrokePath(context);
6269  }
6270 }
6271 
6272 - (void)blink
6273 {
6274  if (dropOperation !== CPTableViewDropOn)
6275  return;
6276 
6277  isBlinking = YES;
6278 
6279  var showCallback = function()
6280  {
6281  objj_msgSend(self, "setHidden:", NO)
6282  isBlinking = NO;
6283  };
6284 
6285  var hideCallback = function()
6286  {
6287  objj_msgSend(self, "setHidden:", YES)
6288  isBlinking = YES;
6289  };
6290 
6291  objj_msgSend(self, "setHidden:", YES);
6292  [CPTimer scheduledTimerWithTimeInterval:0.1 callback:showCallback repeats:NO];
6293  [CPTimer scheduledTimerWithTimeInterval:0.19 callback:hideCallback repeats:NO];
6294  [CPTimer scheduledTimerWithTimeInterval:0.27 callback:showCallback repeats:NO];
6295 }
6296 
6297 @end
6298 
6299 
6300 @implementation _CPColumnDragDrawingView : CPView
6301 {
6305 }
6306 
6307 - (void)drawRect:(CGRect)dirtyRect
6308 {
6309  var context = [[CPGraphicsContext currentContext] graphicsPort],
6310  columnRect = [tableView rectOfColumn:columnIndex],
6311  headerHeight = CGRectGetHeight([[tableView headerView] frame]),
6312  bounds = [columnClipView bounds],
6313  visibleRect = [tableView visibleRect],
6314  xScroll = CGRectGetMinX(visibleRect),
6315  yScroll = CGRectGetMinY(visibleRect);
6316 
6317  // Because we are sharing drawing code with regular table drawing,
6318  // we have to play a few tricks to fool the drawing code into thinking
6319  // our drag column is in the same place as the real column.
6320 
6321  // Shift the bounds origin to align with the column rect, and extend it vertically to ensure
6322  // it reaches the bottom of the tableView when scrolled.
6323  bounds.origin.x = CGRectGetMinX(columnRect) - xScroll;
6324  bounds.size.height += yScroll;
6325 
6326  // Fix up the CTM to account for the header and scroll
6327  CGContextTranslateCTM(context, -bounds.origin.x, headerHeight - yScroll);
6328 
6329  //[tableView drawBackgroundInClipRect:bounds];
6330 
6331  if (tableView._draggedColumnIsSelected)
6332  {
6333  CGContextSetFillColor(context, [tableView selectionHighlightColor]);
6334  CGContextFillRect(context, bounds);
6335  }
6336  else
6337  [tableView highlightSelectionInClipRect:bounds];
6338 
6339  //[tableView _drawHorizontalGridInClipRect:bounds];
6340 
6341  var minX = CGRectGetMinX(bounds) + 0.5,
6342  maxX = CGRectGetMaxX(bounds) - 0.5;
6343 
6344  CGContextSetLineWidth(context, 1.0);
6345  CGContextSetAlpha(context, 1.0);
6346  CGContextSetStrokeColor(context, [tableView gridColor]);
6347 
6348  CGContextBeginPath(context);
6349 
6350  CGContextMoveToPoint(context, minX, CGRectGetMinY(bounds));
6351  CGContextAddLineToPoint(context, minX, CGRectGetMaxY(bounds));
6352 
6353  CGContextMoveToPoint(context, maxX, CGRectGetMinY(bounds));
6354  CGContextAddLineToPoint(context, maxX, CGRectGetMaxY(bounds));
6355 
6356  CGContextStrokePath(context);
6357 }
6358 
6359 @end
6360 
6361 @implementation CPTableCellView : CPView
6362 {
6363  id _objectValue;
6364 
6365  CPTextField _textField;
6366  CPImageView _imageView;
6367 }
6368 
6369 - (void)awakeFromCib
6370 {
6371  [self setThemeState:CPThemeStateTableDataView];
6372 }
6373 
6374 - (BOOL)setThemeState:(ThemeState)aState
6375 {
6376  if (aState.isa && [aState isKindOfClass:CPArray])
6377  aState = CPThemeState.apply(null, aState);
6378 
6379  [super setThemeState:aState];
6380  [self recursivelyPerformSelector:@selector(setThemeState:) withObject:aState startingFrom:self];
6381 }
6382 
6383 - (BOOL)unsetThemeState:(ThemeState)aState
6384 {
6385  if (aState.isa && [aState isKindOfClass:CPArray])
6386  aState = CPThemeState.apply(null, aState);
6387 
6388  [super unsetThemeState:aState];
6389  [self recursivelyPerformSelector:@selector(unsetThemeState:) withObject:aState startingFrom:self];
6390 }
6391 
6392 - (void)recursivelyPerformSelector:(SEL)selector withObject:(id)anObject startingFrom:(id)aView
6393 {
6394  // Avoid infinite loop if a subview is a CPTableCellView.[[aView subviews] enumerateObjectsUsingBlock:function(view, idx)
6395  {
6396  [view performSelector:selector withObject:anObject];
6397 
6398  if (![view isKindOfClass:[self class]])
6399  [self recursivelyPerformSelector:selector withObject:anObject startingFrom:view];
6400  }];
6401 }
6402 
6403 - (CPString)description
6404 {
6405  return "<" + [self className] + " 0x" + [CPString stringWithHash:[self UID]] + " identifier=" + [self identifier] + ">";
6406 }
6407 
6408 @end
6409 
6411 
6415 - (BOOL)disableAutomaticResizing
6416 {
6417  return _disableAutomaticResizing;
6418 }
6419 
6423 - (void)setDisableAutomaticResizing:(BOOL)aValue
6424 {
6425  _disableAutomaticResizing = aValue;
6426 }
6427 
6428 @end
6429 
6431 
6435 - (id)content
6436 {
6437  return _content;
6438 }
6439 
6443 - (void)setContent:(id)aValue
6444 {
6445  _content = aValue;
6446 }
6447 
6448 @end
6449 
6451 
6455 - (id)objectValue
6456 {
6457  return _objectValue;
6458 }
6459 
6463 - (void)setObjectValue:(id)aValue
6464 {
6465  _objectValue = aValue;
6466 }
6467 
6471 - (CPTextField)textField
6472 {
6473  return _textField;
6474 }
6475 
6479 - (void)setTextField:(CPTextField)aValue
6480 {
6481  _textField = aValue;
6482 }
6483 
6487 - (CPImageView)imageView
6488 {
6489  return _imageView;
6490 }
6491 
6495 - (void)setImageView:(CPImageView)aValue
6496 {
6497  _imageView = aValue;
6498 }
6499 
6500 @end