API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPTableView.j
Go to the documentation of this file.
1 /*
2  * CPTableView.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2009, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
25 @class CPButton
26 @class CPClipView
27 @class CPUserDefaults
28 @class CPTableHeaderView
29 @class CPClipView
30 @class CPButton
31 
32 @global CPApp
33 
34 
35 CPTableViewColumnDidMoveNotification = @"CPTableViewColumnDidMoveNotification";
36 CPTableViewColumnDidResizeNotification = @"CPTableViewColumnDidResizeNotification";
37 CPTableViewSelectionDidChangeNotification = @"CPTableViewSelectionDidChangeNotification";
38 CPTableViewSelectionIsChangingNotification = @"CPTableViewSelectionIsChangingNotification";
39 
40 var CPTableViewDataSource_numberOfRowsInTableView_ = 1 << 0,
41  CPTableViewDataSource_tableView_objectValueForTableColumn_row_ = 1 << 1,
42  CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_ = 1 << 2,
43  CPTableViewDataSource_tableView_acceptDrop_row_dropOperation_ = 1 << 3,
44  CPTableViewDataSource_tableView_namesOfPromisedFilesDroppedAtDestination_forDraggedRowsWithIndexes_ = 1 << 4,
45  CPTableViewDataSource_tableView_validateDrop_proposedRow_proposedDropOperation_ = 1 << 5,
46  CPTableViewDataSource_tableView_writeRowsWithIndexes_toPasteboard_ = 1 << 6,
47 
48  CPTableViewDataSource_tableView_sortDescriptorsDidChange_ = 1 << 7;
49 
50 var CPTableViewDelegate_selectionShouldChangeInTableView_ = 1 << 0,
51  CPTableViewDelegate_tableView_viewForTableColumn_row_ = 1 << 1,
52  CPTableViewDelegate_tableView_dataViewForTableColumn_row_ = 1 << 2,
53  CPTableViewDelegate_tableView_didClickTableColumn_ = 1 << 3,
54  CPTableViewDelegate_tableView_didDragTableColumn_ = 1 << 4,
55  CPTableViewDelegate_tableView_heightOfRow_ = 1 << 5,
56  CPTableViewDelegate_tableView_isGroupRow_ = 1 << 6,
57  CPTableViewDelegate_tableView_mouseDownInHeaderOfTableColumn_ = 1 << 7,
58  CPTableViewDelegate_tableView_nextTypeSelectMatchFromRow_toRow_forString_ = 1 << 8,
59  CPTableViewDelegate_tableView_selectionIndexesForProposedSelection_ = 1 << 9,
60  CPTableViewDelegate_tableView_shouldEditTableColumn_row_ = 1 << 10,
61  CPTableViewDelegate_tableView_shouldSelectRow_ = 1 << 11,
62  CPTableViewDelegate_tableView_shouldSelectTableColumn_ = 1 << 12,
63  CPTableViewDelegate_tableView_shouldShowViewExpansionForTableColumn_row_ = 1 << 13,
64  CPTableViewDelegate_tableView_shouldTrackView_forTableColumn_row_ = 1 << 14,
65  CPTableViewDelegate_tableView_shouldTypeSelectForEvent_withCurrentSearchString_ = 1 << 15,
66  CPTableViewDelegate_tableView_toolTipForView_rect_tableColumn_row_mouseLocation_ = 1 << 16,
67  CPTableViewDelegate_tableView_typeSelectStringForTableColumn_row_ = 1 << 17,
68  CPTableViewDelegate_tableView_willDisplayView_forTableColumn_row_ = 1 << 18,
69  CPTableViewDelegate_tableViewSelectionDidChange_ = 1 << 19,
70  CPTableViewDelegate_tableViewSelectionIsChanging_ = 1 << 20,
71  CPTableViewDelegate_tableViewMenuForTableColumn_row_ = 1 << 21,
72  CPTableViewDelegate_tableView_shouldReorderColumn_toColumn_ = 1 << 22;
73 
74 //CPTableViewDraggingDestinationFeedbackStyles
75 CPTableViewDraggingDestinationFeedbackStyleNone = -1;
76 CPTableViewDraggingDestinationFeedbackStyleRegular = 0;
77 CPTableViewDraggingDestinationFeedbackStyleSourceList = 1;
78 
79 //CPTableViewDropOperations
80 CPTableViewDropOn = 0;
81 CPTableViewDropAbove = 1;
82 
83 CPSourceListGradient = @"CPSourceListGradient";
84 CPSourceListTopLineColor = @"CPSourceListTopLineColor";
85 CPSourceListBottomLineColor = @"CPSourceListBottomLineColor";
86 
87 // TODO: add docs
88 
89 CPTableViewSelectionHighlightStyleNone = -1;
90 CPTableViewSelectionHighlightStyleRegular = 0;
91 CPTableViewSelectionHighlightStyleSourceList = 1;
92 
93 CPTableViewGridNone = 0;
94 CPTableViewSolidVerticalGridLineMask = 1 << 0;
95 CPTableViewSolidHorizontalGridLineMask = 1 << 1;
96 
97 CPTableViewNoColumnAutoresizing = 0;
98 CPTableViewUniformColumnAutoresizingStyle = 1; // FIX ME: This is FUBAR
99 CPTableViewSequentialColumnAutoresizingStyle = 2;
100 CPTableViewReverseSequentialColumnAutoresizingStyle = 3;
101 CPTableViewLastColumnOnlyAutoresizingStyle = 4;
102 CPTableViewFirstColumnOnlyAutoresizingStyle = 5;
103 
104 #define NUMBER_OF_COLUMNS() (_tableColumns.length)
105 #define UPDATE_COLUMN_RANGES_IF_NECESSARY() if (_dirtyTableColumnRangeIndex !== CPNotFound) [self _recalculateTableColumnRanges];
106 #define FULL_ROW_HEIGHT() (_rowHeight + _intercellSpacing.height)
107 #define ROW_BOTTOM(__heightInfo) (__heightInfo.y + __heightInfo.height + _intercellSpacing.height)
108 #define HAS_VARIABLE_ROW_HEIGHTS() (_implementedDelegateMethods & CPTableViewDelegate_tableView_heightOfRow_)
109 
110 @implementation _CPTableDrawView : CPView
111 {
112  CPTableView _tableView;
113 }
114 
115 - (id)initWithTableView:(CPTableView)aTableView
116 {
117  self = [super init];
118 
119  if (self)
120  _tableView = aTableView;
121 
122  return self;
123 }
124 
125 - (void)drawRect:(CGRect)aRect
126 {
127  var frame = [self frame],
129 
130  CGContextTranslateCTM(context, -CGRectGetMinX(frame), -CGRectGetMinY(frame));
131 
132  [_tableView _drawRect:aRect];
133 }
134 
135 @end
136 
160 @implementation CPTableView : CPControl
161 {
162  id _dataSource;
163  CPInteger _implementedDataSourceMethods;
164 
165  id _delegate;
166  CPInteger _implementedDelegateMethods;
167 
168  CPArray _tableColumns;
169  CPArray _tableColumnRanges;
170  CPInteger _dirtyTableColumnRangeIndex;
171  CPInteger _numberOfHiddenColumns;
172 
173  BOOL _reloadAllRows;
174  Object _objectValues;
175 
176  CGRect _exposedRect;
177  CPIndexSet _exposedRows;
178  CPIndexSet _exposedColumns;
179 
180  Object _dataViewsForTableColumns;
181  Object _cachedDataViews;
182  CPDictionary _archivedDataViews;
183  Object _unavailable_custom_cibs;
184 
185  //Configuring Behavior
186  BOOL _allowsColumnReordering;
187  BOOL _allowsColumnResizing;
188  BOOL _allowsColumnSelection;
189  BOOL _allowsMultipleSelection;
190  BOOL _allowsEmptySelection;
191 
192  CPArray _sortDescriptors;
193 
194  //Setting Display Attributes
195  CGSize _intercellSpacing;
196  float _rowHeight;
197 
198  BOOL _usesAlternatingRowBackgroundColors;
199  CPArray _alternatingRowBackgroundColors;
200 
201  unsigned _selectionHighlightStyle;
202  CPColor _unfocusedSelectionHighlightColor;
203  CPDictionary _unfocusedSourceListSelectionColor;
204  CPTableColumn _currentHighlightedTableColumn;
205  unsigned _gridStyleMask;
206 
207  unsigned _numberOfRows;
208  CPIndexSet _groupRows;
209 
210  CPArray _cachedRowHeights;
211 
212  // Persistence
213  CPString _autosaveName;
214  BOOL _autosaveTableColumns;
215 
216  CPTableHeaderView _headerView;
217  _CPCornerView _cornerView;
218 
219  CPIndexSet _selectedColumnIndexes;
220  CPIndexSet _selectedRowIndexes;
221  CPInteger _selectionAnchorRow;
222  CPInteger _lastSelectedRow;
223  CPIndexSet _previouslySelectedRowIndexes;
224  CGPoint _startTrackingPoint;
225  CPDate _startTrackingTimestamp;
226  BOOL _trackingPointMovedOutOfClickSlop;
227  CGPoint _editingCellIndex;
228  CPInteger _editingRow;
229  CPInteger _editingColumn;
230 
231  _CPTableDrawView _tableDrawView;
232 
233  SEL _doubleAction;
234  CPInteger _clickedRow;
235  CPInteger _clickedColumn;
236  unsigned _columnAutoResizingStyle;
237 
238  int _lastTrackedRowIndex;
239  CGPoint _originalMouseDownPoint;
240  BOOL _verticalMotionCanDrag;
241  unsigned _destinationDragStyle;
242  BOOL _isSelectingSession;
243  CPIndexSet _draggedRowIndexes;
244  BOOL _wasSelectionBroken;
245 
246  _CPDropOperationDrawingView _dropOperationFeedbackView;
247  CPDragOperation _dragOperationDefaultMask;
248  int _retargetedDropRow;
249  CPDragOperation _retargetedDropOperation;
250  CPArray _draggingViews;
251 
252  BOOL _disableAutomaticResizing;
253  BOOL _lastColumnShouldSnap;
254  BOOL _implementsCustomDrawRow;
255  BOOL _isViewBased;
256  BOOL _contentBindingExplicitlySet;
257 
258  SEL _viewForTableColumnRowSelector;
259 
260  CPTableColumn _draggedColumn;
261  CPArray _differedColumnDataToRemove;
262 }
263 
267 + (CPString)defaultThemeClass
268 {
269  return @"tableview";
270 }
271 
275 + (CPDictionary)themeAttributes
276 {
277  return @{
278  @"alternating-row-colors": [CPNull null],
279  @"grid-color": [CPNull null],
280  @"grid-line-thickness": 1.0,
281  @"highlighted-grid-color": [CPNull null],
282  @"selection-color": [CPNull null],
283  @"sourcelist-selection-color": [CPNull null],
284  @"sort-image": [CPNull null],
285  @"sort-image-reversed": [CPNull null],
286  @"selection-radius": [CPNull null],
287  @"image-generic-file": [CPNull null],
288  @"default-row-height": 25.0,
289  @"dropview-on-background-color": [CPNull null],
290  @"dropview-on-border-color": [CPNull null],
291  @"dropview-on-border-width": [CPNull null],
292  @"dropview-on-border-radius": [CPNull null],
293  @"dropview-on-selected-background-color": [CPNull null],
294  @"dropview-on-selected-border-color": [CPNull null],
295  @"dropview-on-selected-border-width": [CPNull null],
296  @"dropview-on-selected-border-radius": [CPNull null],
297  @"dropview-above-border-color": [CPNull null],
298  @"dropview-above-border-width": [CPNull null],
299  @"dropview-above-selected-border-color": [CPNull null],
300  @"dropview-above-selected-border-width": [CPNull null]
301  };
302 }
303 
304 - (id)initWithFrame:(CGRect)aFrame
305 {
306  self = [super initWithFrame:aFrame];
307 
308  if (self)
309  {
310  //Configuring Behavior
311  _allowsColumnReordering = YES;
312  _allowsColumnResizing = YES;
313  _allowsMultipleSelection = NO;
314  _allowsEmptySelection = YES;
315  _allowsColumnSelection = NO;
316  _disableAutomaticResizing = NO;
317 
318  //Setting Display Attributes
319  _selectionHighlightStyle = CPTableViewSelectionHighlightStyleRegular;
320 
321  [self setUsesAlternatingRowBackgroundColors:NO];
322  [self setAlternatingRowBackgroundColors:
323  [[CPColor whiteColor], [CPColor colorWithRed:245.0 / 255.0 green:249.0 / 255.0 blue:252.0 / 255.0 alpha:1.0]]];
324 
325  _tableColumns = [];
326  _tableColumnRanges = [];
327  _dirtyTableColumnRangeIndex = CPNotFound;
328  _numberOfHiddenColumns = 0;
329 
330  _intercellSpacing = CGSizeMake(3.0, 2.0);
331  _rowHeight = [self valueForThemeAttribute:@"default-row-height"];
332 
333  [self setGridColor:[CPColor colorWithHexString:@"dce0e2"]];
334  [self setGridStyleMask:CPTableViewGridNone];
335 
336  [self setHeaderView:[[CPTableHeaderView alloc] initWithFrame:CGRectMake(0, 0, [self bounds].size.width, _rowHeight)]];
337  [self setCornerView:[[_CPCornerView alloc] initWithFrame:CGRectMake(0, 0, [CPScroller scrollerWidth], CGRectGetHeight([_headerView frame]))]];
338 
339  _currentHighlightedTableColumn = nil;
340 
341  _draggedRowIndexes = [CPIndexSet indexSet];
342  _verticalMotionCanDrag = YES;
343  _isSelectingSession = NO;
344  _retargetedDropRow = nil;
345  _retargetedDropOperation = nil;
346  _dragOperationDefaultMask = nil;
347  _destinationDragStyle = CPTableViewDraggingDestinationFeedbackStyleRegular;
348  _contentBindingExplicitlySet = NO;
349 
350  [self setBackgroundColor:[CPColor whiteColor]];
351  [self _init];
352  }
353 
354  return self;
355 }
356 
357 
363 - (void)_init
364 {
365  _lastSelectedRow = _clickedColumn = _clickedRow = -1;
366 
367  _selectedColumnIndexes = [CPIndexSet indexSet];
368  _selectedRowIndexes = [CPIndexSet indexSet];
369 
370  _dropOperationFeedbackView = [[_CPDropOperationDrawingView alloc] initWithFrame:CGRectMakeZero()];
371  [_dropOperationFeedbackView setTableView:self];
372 
373  _lastColumnShouldSnap = NO;
374 
375  if (!_alternatingRowBackgroundColors)
376  _alternatingRowBackgroundColors = [[CPColor whiteColor], [CPColor colorWithHexString:@"e4e7ff"]];
377 
378  _tableColumnRanges = [];
379  _dirtyTableColumnRangeIndex = 0;
380  _numberOfHiddenColumns = 0;
381 
382  _objectValues = { };
383  _dataViewsForTableColumns = { };
384  _numberOfRows = 0;
385  _exposedRows = [CPIndexSet indexSet];
386  _exposedColumns = [CPIndexSet indexSet];
387  _cachedDataViews = { };
388  _archivedDataViews = nil;
389  _viewForTableColumnRowSelector = nil;
390  _unavailable_custom_cibs = { };
391  _cachedRowHeights = [];
392 
393  _groupRows = [CPIndexSet indexSet];
394 
395  _tableDrawView = [[_CPTableDrawView alloc] initWithTableView:self];
396  [_tableDrawView setBackgroundColor:[CPColor clearColor]];
397  [self addSubview:_tableDrawView];
398 
399  _draggedColumn = nil;
400  _draggingViews = [CPArray array];
401 
402  _editingRow = CPNotFound;
403  _editingColumn = CPNotFound;
404 
405 /* //gradients for the source list when CPTableView is NOT first responder or the window is NOT key
406  // FIX ME: we need to actually implement this.
407  _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);
408  _sourceListInactiveTopLineColor = [CPColor colorWithCalibratedRed:(173.0/255.0) green:(187.0/255.0) blue:(209.0/255.0) alpha:1.0];
409  _sourceListInactiveBottomLineColor = [CPColor colorWithCalibratedRed:(150.0/255.0) green:(161.0/255.0) blue:(183.0/255.0) alpha:1.0];*/
410  _differedColumnDataToRemove = [];
411  _implementsCustomDrawRow = [self implementsSelector:@selector(drawRow:clipRect:)];
412 
413  if (!_sortDescriptors)
414  _sortDescriptors = [];
415 
416  [self _startObservingFirstResponder];
417 }
418 
484 - (void)setDataSource:(id)aDataSource
485 {
486  if (_dataSource === aDataSource)
487  return;
488 
489  _dataSource = aDataSource;
490  _implementedDataSourceMethods = 0;
491 
492  if (!_dataSource)
493  return;
494 
495  var hasContentBinding = !![self infoForBinding:@"content"];
496 
497  if ([_dataSource respondsToSelector:@selector(numberOfRowsInTableView:)])
498  _implementedDataSourceMethods |= CPTableViewDataSource_numberOfRowsInTableView_;
499 
500  if ([_dataSource respondsToSelector:@selector(tableView:objectValueForTableColumn:row:)])
501  _implementedDataSourceMethods |= CPTableViewDataSource_tableView_objectValueForTableColumn_row_;
502 
503  if ([_dataSource respondsToSelector:@selector(tableView:setObjectValue:forTableColumn:row:)])
504  _implementedDataSourceMethods |= CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_;
505 
506  if ([_dataSource respondsToSelector:@selector(tableView:acceptDrop:row:dropOperation:)])
507  _implementedDataSourceMethods |= CPTableViewDataSource_tableView_acceptDrop_row_dropOperation_;
508 
509  if ([_dataSource respondsToSelector:@selector(tableView:namesOfPromisedFilesDroppedAtDestination:forDraggedRowsWithIndexes:)])
510  _implementedDataSourceMethods |= CPTableViewDataSource_tableView_namesOfPromisedFilesDroppedAtDestination_forDraggedRowsWithIndexes_;
511 
512  if ([_dataSource respondsToSelector:@selector(tableView:validateDrop:proposedRow:proposedDropOperation:)])
513  _implementedDataSourceMethods |= CPTableViewDataSource_tableView_validateDrop_proposedRow_proposedDropOperation_;
514 
515  if ([_dataSource respondsToSelector:@selector(tableView:writeRowsWithIndexes:toPasteboard:)])
516  _implementedDataSourceMethods |= CPTableViewDataSource_tableView_writeRowsWithIndexes_toPasteboard_;
517 
518  if ([_dataSource respondsToSelector:@selector(tableView:sortDescriptorsDidChange:)])
519  _implementedDataSourceMethods |= CPTableViewDataSource_tableView_sortDescriptorsDidChange_;
520 
521  [self reloadData];
522 }
523 
527 - (id)dataSource
528 {
529  return _dataSource;
530 }
531 
532 //Loading Data
533 
539 - (void)reloadDataForRowIndexes:(CPIndexSet)rowIndexes columnIndexes:(CPIndexSet)columnIndexes
540 {
541  [self reloadData];
542 // [_previouslyExposedRows removeIndexes:rowIndexes];
543 // [_previouslyExposedColumns removeIndexes:columnIndexes];
544 }
545 
549 - (void)reloadData
550 {
551  //if (!_dataSource)
552  // return;
553 
554  _reloadAllRows = YES;
555  _objectValues = { };
556  _cachedRowHeights = [];
557 
558  // Otherwise, if we have a row marked as group with a
559  // index greater than the new number or rows
560  // it keeps the the graphical group style.
561  [_groupRows removeAllIndexes];
562 
563  // This updates the size too.
564  [self noteNumberOfRowsChanged];
565 
566  [self setNeedsLayout];
567  [self setNeedsDisplay:YES];
568 }
569 
570 //Target-action Behavior
577 - (void)setDoubleAction:(SEL)anAction
578 {
579  _doubleAction = anAction;
580 }
581 
585 - (SEL)doubleAction
586 {
587  return _doubleAction;
588 }
589 
590 /*
591  Returns the index of the the column the user clicked to trigger an action, or -1 if no column was clicked.
592 */
593 - (CPInteger)clickedColumn
594 {
595  return _clickedColumn;
596 }
597 
601 - (CPInteger)clickedRow
602 {
603  return _clickedRow;
604 }
605 
606 //Configuring Behavior
607 
611 - (void)setAllowsColumnReordering:(BOOL)shouldAllowColumnReordering
612 {
613  _allowsColumnReordering = !!shouldAllowColumnReordering;
614 }
615 
619 - (BOOL)allowsColumnReordering
620 {
621  return _allowsColumnReordering;
622 }
623 
628 - (void)setAllowsColumnResizing:(BOOL)shouldAllowColumnResizing
629 {
630  _allowsColumnResizing = !!shouldAllowColumnResizing;
631 }
632 
636 - (BOOL)allowsColumnResizing
637 {
638  return _allowsColumnResizing;
639 }
640 
645 - (void)setAllowsMultipleSelection:(BOOL)shouldAllowMultipleSelection
646 {
647  _allowsMultipleSelection = !!shouldAllowMultipleSelection;
648 }
649 
655 - (BOOL)allowsMultipleSelection
656 {
657  return _allowsMultipleSelection;
658 }
659 
664 - (void)setAllowsEmptySelection:(BOOL)shouldAllowEmptySelection
665 {
666  _allowsEmptySelection = !!shouldAllowEmptySelection;
667 }
668 
672 - (BOOL)allowsEmptySelection
673 {
674  return _allowsEmptySelection;
675 }
676 
682 - (void)setAllowsColumnSelection:(BOOL)shouldAllowColumnSelection
683 {
684  _allowsColumnSelection = !!shouldAllowColumnSelection;
685 }
686 
687 
691 - (BOOL)allowsColumnSelection
692 {
693  return _allowsColumnSelection;
694 }
695 
696 //Setting Display Attributes
703 - (void)setIntercellSpacing:(CGSize)aSize
704 {
705  if (CGSizeEqualToSize(_intercellSpacing, aSize))
706  return;
707 
708  _intercellSpacing = CGSizeMakeCopy(aSize);
709 
710  _dirtyTableColumnRangeIndex = 0; // so that _recalculateTableColumnRanges will work
711  [self _recalculateTableColumnRanges];
712 
713  [self setNeedsLayout];
714  [_headerView setNeedsDisplay:YES];
715  [_headerView setNeedsLayout];
716 
717  [self reloadData];
718 }
719 
723 - (CGSize)intercellSpacing
724 {
725  return CGSizeMakeCopy(_intercellSpacing);
726 }
727 
734 - (void)setRowHeight:(unsigned)aRowHeight
735 {
736  // Accept row heights such as "0".
737  aRowHeight = +aRowHeight;
738 
739  if (_rowHeight === aRowHeight)
740  return;
741 
742  _rowHeight = MAX(0.0, aRowHeight);
743 
744  [self setNeedsLayout];
745 }
746 
750 - (unsigned)rowHeight
751 {
752  return _rowHeight;
753 }
754 
760 - (void)setUsesAlternatingRowBackgroundColors:(BOOL)shouldUseAlternatingRowBackgroundColors
761 {
762  _usesAlternatingRowBackgroundColors = shouldUseAlternatingRowBackgroundColors;
763 }
764 
768 - (BOOL)usesAlternatingRowBackgroundColors
769 {
770  return _usesAlternatingRowBackgroundColors;
771 }
772 
778 - (void)setAlternatingRowBackgroundColors:(CPArray)alternatingRowBackgroundColors
779 {
780  [self setValue:alternatingRowBackgroundColors forThemeAttribute:@"alternating-row-colors"];
781 
782  [self setNeedsDisplay:YES];
783 }
784 
788 - (CPArray)alternatingRowBackgroundColors
789 {
790  return [self currentValueForThemeAttribute:@"alternating-row-colors"];
791 }
792 
804 - (unsigned)selectionHighlightStyle
805 {
806  return _selectionHighlightStyle;
807 }
808 
820 - (void)setSelectionHighlightStyle:(unsigned)aSelectionHighlightStyle
821 {
822  _selectionHighlightStyle = aSelectionHighlightStyle;
823 
824  if (aSelectionHighlightStyle === CPTableViewSelectionHighlightStyleSourceList)
825  _destinationDragStyle = CPTableViewDraggingDestinationFeedbackStyleSourceList;
826  else
827  _destinationDragStyle = CPTableViewDraggingDestinationFeedbackStyleRegular;
828 
829  [self _updateHighlightWithOldRows:[CPIndexSet indexSet] newRows:_selectedRowIndexes];
830  [self _updateHighlightWithOldColumns:[CPIndexSet indexSet] newColumns:_selectedColumnIndexes];
831  [self setNeedsDisplay:YES];
832 }
833 
839 - (void)setSelectionHighlightColor:(CPColor)aColor
840 {
841  if ([[self selectionHighlightColor] isEqual:aColor])
842  return;
843 
844  [self setValue:aColor forThemeAttribute:@"selection-color"];
845  [self setNeedsDisplay:YES];
846 }
847 
851 - (CPColor)selectionHighlightColor
852 {
853  return [self currentValueForThemeAttribute:@"selection-color"];
854 }
855 
859 - (CPColor)unfocusedSelectionHighlightColor
860 {
861  if (!_unfocusedSelectionHighlightColor)
862  _unfocusedSelectionHighlightColor = [self _unfocusedSelectionColorFromColor:[self selectionHighlightColor] saturation:0];
863 
864  return _unfocusedSelectionHighlightColor;
865 }
866 
878 - (void)setSelectionGradientColors:(CPDictionary)aDictionary
879 {
880  [self setValue:aDictionary forThemeAttribute:@"sourcelist-selection-color"];
881  [self setNeedsDisplay:YES];
882 }
883 
892 - (CPDictionary)selectionGradientColors
893 {
894  return [self currentValueForThemeAttribute:@"sourcelist-selection-color"];
895 }
896 
906 - (void)unfocusedSelectionGradientColors
907 {
908  if (!_unfocusedSourceListSelectionColor)
909  {
910  var sourceListColors = [self selectionGradientColors];
911 
912  _unfocusedSourceListSelectionColor = @{
913  CPSourceListGradient: [self _unfocusedGradientFromGradient:[sourceListColors objectForKey:CPSourceListGradient]],
914  CPSourceListTopLineColor: [self _unfocusedSelectionColorFromColor:[sourceListColors objectForKey:CPSourceListTopLineColor] saturation:0.2],
915  CPSourceListBottomLineColor: [self _unfocusedSelectionColorFromColor:[sourceListColors objectForKey:CPSourceListBottomLineColor] saturation:0.2]
916  };
917  }
918 
919  return _unfocusedSourceListSelectionColor;
920 }
921 
922 - (CPColor)_unfocusedSelectionColorFromColor:(CPColor)aColor saturation:(float)saturation
923 {
924  var hsb = [aColor hsbComponents];
925 
926  return [CPColor colorWithHue:hsb[0] saturation:hsb[1] * saturation brightness:hsb[2]];
927 }
928 
929 - (CGGradient)_unfocusedGradientFromGradient:(CGGradient)aGradient
930 {
931  var colors = [aGradient.colors copy],
932  count = [colors count];
933 
934  while (count--)
935  {
936  var rgba = colors[count].components,
937  hsb = [self _unfocusedSelectionColorFromColor:[CPColor colorWithRed:rgba[0] green:rgba[1] blue:rgba[2] alpha:rgba[3]] saturation:0.2];
938 
939  colors[count] = CGColorCreate(aGradient.colorspace, [[hsb components] copy]);
940  }
941 
942  return CGGradientCreateWithColors(aGradient.colorspace, colors, aGradient.locations);
943 }
944 
949 - (void)setGridColor:(CPColor)aColor
950 {
951  [self setValue:aColor forThemeAttribute:@"grid-color"];
952 
953  [self setNeedsDisplay:YES];
954 }
955 
959 - (CPColor)gridColor
960 {
961  return [self currentValueForThemeAttribute:@"grid-color"];;
962 }
963 
969 - (void)setGridStyleMask:(unsigned)aGrideStyleMask
970 {
971  if (_gridStyleMask === aGrideStyleMask)
972  return;
973 
974  _gridStyleMask = aGrideStyleMask;
975 
976  [self setNeedsDisplay:YES];
977 }
978 
982 - (unsigned)gridStyleMask
983 {
984  return _gridStyleMask;
985 }
986 
987 //Column Management
988 
993 - (void)addTableColumn:(CPTableColumn)aTableColumn
994 {
995  [_tableColumns addObject:aTableColumn];
996  [aTableColumn setTableView:self];
997 
998  if (_dirtyTableColumnRangeIndex < 0)
999  _dirtyTableColumnRangeIndex = NUMBER_OF_COLUMNS() - 1;
1000  else
1001  _dirtyTableColumnRangeIndex = MIN(NUMBER_OF_COLUMNS() - 1, _dirtyTableColumnRangeIndex);
1002 
1003  if ([[self sortDescriptors] count] > 0)
1004  {
1005  var mainSortDescriptor = [[self sortDescriptors] objectAtIndex:0];
1006 
1007  if (aTableColumn === [self _tableColumnForSortDescriptor:mainSortDescriptor])
1008  {
1009  var image = [mainSortDescriptor ascending] ? [self _tableHeaderSortImage] : [self _tableHeaderReverseSortImage];
1010  [self setIndicatorImage:image inTableColumn:aTableColumn];
1011  }
1012  }
1013 
1014  [self tile];
1015  [self setNeedsLayout];
1016 }
1017 
1022 - (void)removeTableColumn:(CPTableColumn)aTableColumn
1023 {
1024  if ([aTableColumn tableView] !== self)
1025  return;
1026 
1027  var index = [_tableColumns indexOfObjectIdenticalTo:aTableColumn];
1028 
1029  if (index === CPNotFound)
1030  return;
1031 
1032  // we defer the actual removal until the end of the runloop in order to keep a reference to the column.
1033  [_differedColumnDataToRemove addObject:{"column":aTableColumn, "shouldBeHidden": [aTableColumn isHidden]}];
1034 
1035  [aTableColumn setHidden:YES];
1036  [aTableColumn setTableView:nil];
1037 
1038  var tableColumnUID = [aTableColumn UID];
1039 
1040  if (_objectValues[tableColumnUID])
1041  _objectValues[tableColumnUID] = nil;
1042 
1043  if (_dirtyTableColumnRangeIndex < 0)
1044  _dirtyTableColumnRangeIndex = index;
1045  else
1046  _dirtyTableColumnRangeIndex = MIN(index, _dirtyTableColumnRangeIndex);
1047 
1048  [_tableColumns removeObject:aTableColumn];
1049 
1050  [self setNeedsLayout];
1051 }
1052 
1057 - (void)_setDraggedColumn:(CPTableColumn)aColumn
1058 {
1059  if (_draggedColumn === aColumn)
1060  return;
1061 
1062  var previouslyDraggedColumn = _draggedColumn;
1063  _draggedColumn = aColumn;
1064 
1065  // if a column is currently being dragged, update that column (removing data views)
1066  if (aColumn)
1067  [self reloadDataForRowIndexes:_exposedRows columnIndexes:[CPIndexSet indexSetWithIndex:[_tableColumns indexOfObject:aColumn]]];
1068 
1069  // when the column is dropped, we should also update it.
1070  if (previouslyDraggedColumn)
1071  [self reloadDataForRowIndexes:_exposedRows columnIndexes:[CPIndexSet indexSetWithIndex:[_tableColumns indexOfObject:previouslyDraggedColumn]]];
1072 }
1073 
1074 /*
1075  @ignore
1076  Same as moveColumn:toColumn: but doesn't trigger an autosave
1077 */
1078 - (void)_moveColumn:(unsigned)fromIndex toColumn:(unsigned)toIndex
1079 {
1080  // Convert parameters such as "0" to 0.
1081  fromIndex = +fromIndex;
1082  toIndex = +toIndex;
1083 
1084  if (fromIndex === toIndex)
1085  return;
1086 
1087  if (_dirtyTableColumnRangeIndex < 0)
1088  _dirtyTableColumnRangeIndex = MIN(fromIndex, toIndex);
1089  else
1090  _dirtyTableColumnRangeIndex = MIN(fromIndex, toIndex, _dirtyTableColumnRangeIndex);
1091 
1092  var tableColumn = _tableColumns[fromIndex];
1093 
1094  [_tableColumns removeObjectAtIndex:fromIndex];
1095  [_tableColumns insertObject:tableColumn atIndex:toIndex];
1096 
1097  [[self headerView] setNeedsLayout];
1098  [[self headerView] setNeedsDisplay:YES];
1099 
1100  var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])],
1101  columnIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(fromIndex, toIndex)];
1102 
1103  [self reloadDataForRowIndexes:rowIndexes columnIndexes:columnIndexes];
1104 
1105  // Notify even if programmatically moving a column as in Cocoa.
1106  // TODO Only notify when a column drag operation ends, not each time a column reaches a new slot?
1107  [[CPNotificationCenter defaultCenter] postNotificationName:CPTableViewColumnDidMoveNotification
1108  object:self
1109  userInfo:@{ @"CPOldColumn": fromIndex, @"CPNewColumn": toIndex }];
1110 }
1111 
1117 - (void)moveColumn:(CPInteger)theColumnIndex toColumn:(CPInteger)theToIndex
1118 {
1119  [self _moveColumn:theColumnIndex toColumn:theToIndex];
1120  [self _autosave];
1121 }
1122 
1127 - (void)_tableColumnVisibilityDidChange:(CPTableColumn)aColumn
1128 {
1129  var columnIndex = [[self tableColumns] indexOfObjectIdenticalTo:aColumn];
1130 
1131  if (_dirtyTableColumnRangeIndex < 0)
1132  _dirtyTableColumnRangeIndex = columnIndex;
1133  else
1134  _dirtyTableColumnRangeIndex = MIN(columnIndex, _dirtyTableColumnRangeIndex);
1135 
1136  [[self headerView] setNeedsLayout];
1137  [[self headerView] setNeedsDisplay:YES];
1138 
1139  var rowIndexes = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])];
1140  [self reloadDataForRowIndexes:rowIndexes columnIndexes:[CPIndexSet indexSetWithIndex:columnIndex]];
1141 }
1142 
1146 - (CPArray)tableColumns
1147 {
1148  return _tableColumns;
1149 }
1150 
1157 - (CPInteger)columnWithIdentifier:(CPString)anIdentifier
1158 {
1159  var index = 0,
1160  count = NUMBER_OF_COLUMNS();
1161 
1162  for (; index < count; ++index)
1163  if ([_tableColumns[index] identifier] === anIdentifier)
1164  return index;
1165 
1166  return CPNotFound;
1167 }
1168 
1175 - (CPTableColumn)tableColumnWithIdentifier:(CPString)anIdentifier
1176 {
1177  var index = [self columnWithIdentifier:anIdentifier];
1178 
1179  if (index === CPNotFound)
1180  return nil;
1181 
1182  return _tableColumns[index];
1183 }
1184 
1188 - (void)_didResizeTableColumn:(CPTableColumn)theColumn
1189 {
1190  [self _autosave];
1191 }
1192 
1193 //Selecting Columns and Rows
1194 
1201 - (void)selectColumnIndexes:(CPIndexSet)columns byExtendingSelection:(BOOL)shouldExtendSelection
1202 {
1203  // If we're out of range, just return
1204  if (([columns firstIndex] != CPNotFound && [columns firstIndex] < 0) || [columns lastIndex] >= [self numberOfColumns])
1205  return;
1206 
1207  // We deselect all rows when selecting columns.
1208  if ([_selectedRowIndexes count] > 0)
1209  {
1210  [self _updateHighlightWithOldRows:_selectedRowIndexes newRows:[CPIndexSet indexSet]];
1211  _selectedRowIndexes = [CPIndexSet indexSet];
1212  }
1213 
1214  var previousSelectedIndexes = [_selectedColumnIndexes copy];
1215 
1216  if (shouldExtendSelection)
1217  [_selectedColumnIndexes addIndexes:columns];
1218  else
1219  _selectedColumnIndexes = [columns copy];
1220 
1221  [self _updateHighlightWithOldColumns:previousSelectedIndexes newColumns:_selectedColumnIndexes];
1222  [self setNeedsDisplay:YES]; // FIXME: should be setNeedsDisplayInRect:enclosing rect of new (de)selected columns
1223  // but currently -drawRect: is not implemented here
1224  if (_headerView)
1225  [_headerView setNeedsDisplay:YES];
1226 
1227  [self _noteSelectionDidChange];
1228 }
1229 
1233 - (void)_setSelectedRowIndexes:(CPIndexSet)rows
1234 {
1235  if ([_selectedRowIndexes isEqualToIndexSet:rows])
1236  return;
1237 
1238  var previousSelectedIndexes = _selectedRowIndexes;
1239 
1240  _lastSelectedRow = ([rows count] > 0) ? [rows lastIndex] : -1;
1241  _selectedRowIndexes = [rows copy];
1242 
1243  [self _updateHighlightWithOldRows:previousSelectedIndexes newRows:_selectedRowIndexes];
1244  [self setNeedsDisplay:YES]; // FIXME: should be setNeedsDisplayInRect:enclosing rect of new (de)selected rows
1245  // but currently -drawRect: is not implemented here
1246 
1247  var binderClass = [[self class] _binderClassForBinding:@"selectionIndexes"];
1248  [[binderClass getBinding:@"selectionIndexes" forObject:self] reverseSetValueFor:@"selectedRowIndexes"];
1249 
1250  [self _noteSelectionDidChange];
1251 }
1252 
1259 - (void)selectRowIndexes:(CPIndexSet)rows byExtendingSelection:(BOOL)shouldExtendSelection
1260 {
1261  if ([rows isEqualToIndexSet:_selectedRowIndexes] ||
1262  (([rows firstIndex] != CPNotFound && [rows firstIndex] < 0) || [rows lastIndex] >= [self numberOfRows]) ||
1263  [self numberOfColumns] <= 0)
1264  return;
1265 
1266  // We deselect all columns when selecting rows.
1267  if ([_selectedColumnIndexes count] > 0)
1268  {
1269  [self _updateHighlightWithOldColumns:_selectedColumnIndexes newColumns:[CPIndexSet indexSet]];
1270  _selectedColumnIndexes = [CPIndexSet indexSet];
1271  if (_headerView)
1272  [_headerView setNeedsDisplay:YES];
1273  }
1274 
1275  var newSelectedIndexes;
1276  if (shouldExtendSelection)
1277  {
1278  newSelectedIndexes = [_selectedRowIndexes copy];
1279  [newSelectedIndexes addIndexes:rows];
1280  }
1281  else
1282  newSelectedIndexes = [rows copy];
1283 
1284  [self _setSelectedRowIndexes:newSelectedIndexes];
1285 }
1286 
1290 - (void)_updateHighlightWithOldRows:(CPIndexSet)oldRows newRows:(CPIndexSet)newRows
1291 {
1292  var firstExposedRow = [_exposedRows firstIndex],
1293  exposedLength = [_exposedRows lastIndex] - firstExposedRow + 1,
1294  deselectRows = [],
1295  selectRows = [],
1296  deselectRowIndexes = [oldRows copy],
1297  selectRowIndexes = [newRows copy];
1298 
1299  [deselectRowIndexes removeMatches:selectRowIndexes];
1300  [deselectRowIndexes getIndexes:deselectRows maxCount:-1 inIndexRange:CPMakeRange(firstExposedRow, exposedLength)];
1301  [selectRowIndexes getIndexes:selectRows maxCount:-1 inIndexRange:CPMakeRange(firstExposedRow, exposedLength)];
1302 
1303  var showsSelection = _selectionHighlightStyle !== CPTableViewSelectionHighlightStyleNone,
1304  selectors = [@selector(unsetThemeState:), @selector(setThemeState:)],
1305  selectInfo = [
1306  { rows:deselectRows, selectorIndex:0 },
1307  { rows:selectRows, selectorIndex:showsSelection ? 1 : 0 }
1308  ];
1309 
1310  for (var identifier in _dataViewsForTableColumns)
1311  {
1312  var dataViewsInTableColumn = _dataViewsForTableColumns[identifier];
1313 
1314  for (var i = 0; i < selectInfo.length; ++i)
1315  {
1316  var info = selectInfo[i],
1317  count = info.rows.length;
1318 
1319  while (count--)
1320  {
1321  var view = dataViewsInTableColumn[info.rows[count]];
1322  [view performSelector:selectors[info.selectorIndex] withObject:CPThemeStateSelectedDataView];
1323  }
1324  }
1325  }
1326 }
1327 
1331 - (void)_updateHighlightWithOldColumns:(CPIndexSet)oldColumns newColumns:(CPIndexSet)newColumns
1332 {
1333  var firstExposedColumn = [_exposedColumns firstIndex],
1334  exposedLength = [_exposedColumns lastIndex] - firstExposedColumn +1,
1335  deselectColumns = [],
1336  selectColumns = [],
1337  deselectColumnIndexes = [oldColumns copy],
1338  selectColumnIndexes = [newColumns copy],
1339  selectRows = [];
1340 
1341  [deselectColumnIndexes removeMatches:selectColumnIndexes];
1342  [deselectColumnIndexes getIndexes:deselectColumns maxCount:-1 inIndexRange:CPMakeRange(firstExposedColumn, exposedLength)];
1343  [selectColumnIndexes getIndexes:selectColumns maxCount:-1 inIndexRange:CPMakeRange(firstExposedColumn, exposedLength)];
1344  [_exposedRows getIndexes:selectRows maxCount:-1 inIndexRange:nil];
1345 
1346  var showsSelection = _selectionHighlightStyle !== CPTableViewSelectionHighlightStyleNone,
1347  selectors = [@selector(unsetThemeState:), @selector(setThemeState:)],
1348 
1349  // Rows do not show selection with CPTableViewSelectionHighlightStyleNone, but headers do
1350  selectInfo = [
1351  {
1352  columns:deselectColumns,
1353  rowSelectorIndex:0,
1354  headerSelectorIndex:0
1355  },
1356  {
1357  columns:selectColumns,
1358  rowSelectorIndex:showsSelection ? 1 : 0,
1359  headerSelectorIndex:1
1360  }
1361  ],
1362  rowsCount = selectRows.length;
1363 
1364  for (var selectIndex = 0; selectIndex < selectInfo.length; ++selectIndex)
1365  {
1366  var info = selectInfo[selectIndex],
1367  count = info.columns.length,
1368  rowSelector = selectors[info.rowSelectorIndex],
1369  headerSelector = selectors[info.headerSelectorIndex];
1370 
1371  while (count--)
1372  {
1373  var columnIndex = info.columns[count],
1374  identifier = [_tableColumns[columnIndex] UID],
1375  dataViewsInTableColumn = _dataViewsForTableColumns[identifier];
1376 
1377  for (var i = 0; i < rowsCount; i++)
1378  {
1379  var rowIndex = selectRows[i],
1380  dataView = dataViewsInTableColumn[rowIndex];
1381 
1382  [dataView performSelector:rowSelector withObject:CPThemeStateSelectedDataView];
1383  }
1384 
1385  if (_headerView)
1386  {
1387  var headerView = [_tableColumns[columnIndex] headerView];
1388  [headerView performSelector:headerSelector withObject:CPThemeStateSelected];
1389  }
1390  }
1391  }
1392 }
1393 
1397 - (int)selectedColumn
1398 {
1399  return [_selectedColumnIndexes lastIndex];
1400 }
1401 
1405 - (CPIndexSet)selectedColumnIndexes
1406 {
1407  return _selectedColumnIndexes;
1408 }
1409 
1413 - (int)selectedRow
1414 {
1415  return _lastSelectedRow;
1416 }
1417 
1421 - (CPIndexSet)selectedRowIndexes
1422 {
1423  return [_selectedRowIndexes copy];
1424 }
1425 
1431 - (void)deselectColumn:(CPInteger)anIndex
1432 {
1433  var selectedColumnIndexes = [_selectedColumnIndexes copy];
1434  [selectedColumnIndexes removeIndex:anIndex];
1435  [self selectColumnIndexes:selectedColumnIndexes byExtendingSelection:NO];
1436  [self _noteSelectionDidChange];
1437 }
1438 
1444 - (void)deselectRow:(CPInteger)aRow
1445 {
1446  var selectedRowIndexes = [_selectedRowIndexes copy];
1447  [selectedRowIndexes removeIndex:aRow];
1448  [self selectRowIndexes:selectedRowIndexes byExtendingSelection:NO];
1449  [self _noteSelectionDidChange];
1450 }
1451 
1455 - (CPInteger)numberOfSelectedColumns
1456 {
1457  return [_selectedColumnIndexes count];
1458 }
1459 
1463 - (CPInteger)numberOfSelectedRows
1464 {
1465  return [_selectedRowIndexes count];
1466 }
1467 
1474 - (BOOL)isColumnSelected:(CPInteger)anIndex
1475 {
1476  return [_selectedColumnIndexes containsIndex:anIndex];
1477 }
1478 
1485 - (BOOL)isRowSelected:(CPInteger)aRow
1486 {
1487  return [_selectedRowIndexes containsIndex:aRow];
1488 }
1489 
1490 
1495 - (void)deselectAll
1496 {
1497  [self selectRowIndexes:[CPIndexSet indexSet] byExtendingSelection:NO];
1498  [self selectColumnIndexes:[CPIndexSet indexSet] byExtendingSelection:NO];
1499 }
1500 
1501 - (void)selectAll:(id)sender
1502 {
1503  if (_allowsMultipleSelection)
1504  {
1505  if (![self _sendDelegateSelectionShouldChangeInTableView])
1506  return;
1507 
1508  if ([[self selectedColumnIndexes] count])
1509  [self selectColumnIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfColumns])] byExtendingSelection:NO];
1510  else
1511  [self selectRowIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])] byExtendingSelection:NO];
1512  }
1513 }
1514 
1515 - (void)deselectAll:(id)sender
1516 {
1517  if ([self allowsEmptySelection])
1518  {
1519  if (![self _sendDelegateSelectionShouldChangeInTableView])
1520  return;
1521 
1522  [self deselectAll];
1523  }
1524 }
1525 
1529 - (int)numberOfColumns
1530 {
1531  return NUMBER_OF_COLUMNS();
1532 }
1533 
1537 - (int)numberOfRows
1538 {
1539  if (_numberOfRows !== nil)
1540  return _numberOfRows;
1541 
1542  var contentBindingInfo = [self infoForBinding:@"content"];
1543 
1544  if (contentBindingInfo)
1545  {
1546  var destination = [contentBindingInfo objectForKey:CPObservedObjectKey],
1547  keyPath = [contentBindingInfo objectForKey:CPObservedKeyPathKey];
1548 
1549  _numberOfRows = [[destination valueForKeyPath:keyPath] count];
1550  }
1551  else
1552  {
1553  _numberOfRows = [self _sendDataSourceNumberOfRowsInTableView];
1554 
1555  if (!_numberOfRows)
1556  {
1557  if (_dataSource && ![self _dataSourceRespondsToNumberOfRowsinTableView])
1558  CPLog(@"no content binding established and data source " + [_dataSource description] + " does not implement numberOfRowsInTableView:");
1559 
1560  _numberOfRows = 0;
1561  }
1562  }
1563 
1564  return _numberOfRows;
1565 }
1566 
1576 - (void)editColumn:(CPInteger)columnIndex row:(CPInteger)rowIndex withEvent:(CPEvent)theEvent select:(BOOL)flag
1577 {
1578  // FIX ME: Cocoa documentation says all this should be called in THIS method:
1579  // sets up the field editor, and sends selectWithFrame:inView:editor:delegate:start:length: and editWithFrame:inView:editor:delegate:event: to the field editor's NSCell object with the NSTableView as the text delegate.
1580  if (_isViewBased)
1581  {
1582  var identifier = [_tableColumns[columnIndex] UID],
1583  view = _dataViewsForTableColumns[identifier][rowIndex];
1584 
1585  [[self window] makeFirstResponder:view];
1586  }
1587  else
1588  {
1589  if (![self isRowSelected:rowIndex])
1590  [[CPException exceptionWithName:@"Error" reason:@"Attempt to edit row="+rowIndex+" when not selected." userInfo:nil] raise];
1591 
1592  [self scrollRowToVisible:rowIndex];
1593  [self scrollColumnToVisible:columnIndex];
1594 
1595  // TODO Do something with flag.
1596 
1597  _editingCellIndex = CGPointMake(columnIndex, rowIndex);
1598  _editingCellIndex._shouldSelect = flag;
1599 
1600  [self reloadDataForRowIndexes:[CPIndexSet indexSetWithIndex:rowIndex]
1601  columnIndexes:[CPIndexSet indexSetWithIndex:columnIndex]];
1602  }
1603 }
1604 
1608 - (CPInteger)editedColumn
1609 {
1610  if (!_editingCellIndex)
1611  return CPNotFound;
1612 
1613  return _editingCellIndex.x;
1614 }
1615 
1619 - (CPInteger)editedRow
1620 {
1621  if (!_editingCellIndex)
1622  return CPNotFound;
1623 
1624  return _editingCellIndex.y;
1625 }
1626 
1630 - (CPView)cornerView
1631 {
1632  return _cornerView;
1633 }
1634 
1638 - (void)setCornerView:(CPView)aView
1639 {
1640  if (_cornerView === aView)
1641  return;
1642 
1643  _cornerView = aView;
1644 
1645  var scrollView = [self enclosingScrollView];
1646 
1647  if ([scrollView isKindOfClass:[CPScrollView class]] && [scrollView documentView] === self)
1648  [scrollView _updateCornerAndHeaderView];
1649 }
1650 
1654 - (CPView)headerView
1655 {
1656  return _headerView;
1657 }
1658 
1659 
1667 - (void)setHeaderView:(CPView)aHeaderView
1668 {
1669  if (_headerView === aHeaderView)
1670  return;
1671 
1672  [_headerView setTableView:nil];
1673 
1674  _headerView = aHeaderView;
1675 
1676  if (_headerView)
1677  {
1678  [_headerView setTableView:self];
1679  [_headerView setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), CGRectGetHeight([_headerView frame]))];
1680  }
1681  else
1682  {
1683  // If there is no header view, there should be no corner view
1684  [_cornerView removeFromSuperview];
1685  _cornerView = nil;
1686  }
1687 
1688  var scrollView = [self enclosingScrollView];
1689 
1690  if ([scrollView isKindOfClass:[CPScrollView class]] && [scrollView documentView] === self)
1691  [scrollView _updateCornerAndHeaderView];
1692 
1693  [self setNeedsLayout];
1694 }
1695 
1699 - (void)_recalculateTableColumnRanges
1700 {
1701  // Complexity:
1702  // O(Columns)
1703 
1704  if (_dirtyTableColumnRangeIndex < 0)
1705  return;
1706 
1707  _numberOfHiddenColumns = 0;
1708 
1709  var index = _dirtyTableColumnRangeIndex,
1710  count = NUMBER_OF_COLUMNS(),
1711  x = index === 0 ? 0.0 : CPMaxRange(_tableColumnRanges[index - 1]);
1712 
1713  for (; index < count; ++index)
1714  {
1715  var tableColumn = _tableColumns[index];
1716 
1717  if ([tableColumn isHidden])
1718  {
1719  _numberOfHiddenColumns += 1;
1720  _tableColumnRanges[index] = CPMakeRange(x, 0.0);
1721  }
1722  else
1723  {
1724  var width = [_tableColumns[index] width] + _intercellSpacing.width;
1725 
1726  _tableColumnRanges[index] = CPMakeRange(x, width);
1727 
1728  x += width;
1729  }
1730  }
1731 
1732  _tableColumnRanges.length = count;
1733  _dirtyTableColumnRangeIndex = CPNotFound;
1734 }
1735 
1742 - (CGRect)rectOfColumn:(CPInteger)aColumnIndex
1743 {
1744  // Complexity:
1745  // O(1)
1746 
1747  // Coerce aColumnIndex to a number in case it is a string.
1748  aColumnIndex = +aColumnIndex;
1749 
1750  if (aColumnIndex < 0 || aColumnIndex >= NUMBER_OF_COLUMNS())
1751  return CGRectMakeZero();
1752 
1753  if ([[_tableColumns objectAtIndex:aColumnIndex] isHidden])
1754  return CGRectMakeZero();
1755 
1757 
1758  var range = _tableColumnRanges[aColumnIndex];
1759 
1760  return CGRectMake(range.location, 0.0, range.length, CGRectGetHeight([self bounds]));
1761 }
1762 
1770 - (CGRect)_rectOfRow:(CPInteger)aRowIndex checkRange:(BOOL)checkRange
1771 {
1772  // Complexity:
1773  // O(1)
1774 
1775  var lastIndex = [self numberOfRows] - 1,
1776  validIndex = aRowIndex >= 0 && aRowIndex <= lastIndex;
1777 
1778  if (checkRange && !validIndex)
1779  return CGRectMakeZero();
1780 
1781  var y = 0,
1782  height,
1783  fixedHeightRows = 0;
1784 
1786  {
1787  [self _populateRowHeightCacheIfNeeded];
1788 
1789  // If the index is valid, we use the y and height of the given row.
1790  // If the index is invalid, we start from the bottom of the last row and use the default row height.
1791  var heightInfo;
1792 
1793  if (validIndex)
1794  {
1795  heightInfo = _cachedRowHeights[aRowIndex];
1796  y = heightInfo.y;
1797  height = heightInfo.height + _intercellSpacing.height;
1798  }
1799  else
1800  {
1801  height = FULL_ROW_HEIGHT();
1802 
1803  if (_numberOfRows > 0)
1804  {
1805  heightInfo = _cachedRowHeights[lastIndex];
1806  y = ROW_BOTTOM(heightInfo);
1807 
1808  // y is now at the top of the first row beyond the last valid row.
1809  // Add the height of any rows beyond that.
1810  fixedHeightRows = aRowIndex - _numberOfRows;
1811  }
1812  }
1813  }
1814  else
1815  {
1816  fixedHeightRows = aRowIndex;
1817  height = FULL_ROW_HEIGHT();
1818  }
1819 
1820  y += fixedHeightRows * FULL_ROW_HEIGHT();
1821 
1822  return CGRectMake(0.0, y, CGRectGetWidth([self bounds]), height);
1823 }
1824 
1830 - (CGRect)rectOfRow:(CPInteger)aRowIndex
1831 {
1832  return [self _rectOfRow:aRowIndex checkRange:YES];
1833 }
1834 
1840 - (CPRange)rowsInRect:(CGRect)aRect
1841 {
1842  // Complexity:
1843  // O(1)
1844 
1845  // If we have no rows, then we won't intersect anything.
1846  if (_numberOfRows <= 0)
1847  return CPMakeRange(0, 0);
1848 
1849  var bounds = [self bounds];
1850 
1851  // No rows if the rect doesn't even intersect us.
1852  if (!CGRectIntersectsRect(aRect, bounds))
1853  return CPMakeRange(0, 0);
1854 
1855  var firstRow = [self rowAtPoint:aRect.origin];
1856 
1857  // first row has to be undershot, because if not we wouldn't be intersecting.
1858  if (firstRow < 0)
1859  firstRow = 0;
1860 
1861  var lastRow = [self rowAtPoint:CGPointMake(0.0, CGRectGetMaxY(aRect))];
1862 
1863  // last row has to be overshot, because if not we wouldn't be intersecting.
1864  if (lastRow < 0)
1865  lastRow = _numberOfRows - 1;
1866 
1867  return CPMakeRange(firstRow, lastRow - firstRow + 1);
1868 }
1869 
1870 /*
1871  Return the range of rows that lie wholly or partially within aRect.
1872  If the bottom of the last real row is above the bottom of aRect,
1873  synthesized rows of the default height are added to fill aRect.
1874 */
1875 - (CPRange)_exposedRowsInRect:(CGRect)aRect
1876 {
1877  var rowRange = [self rowsInRect:aRect],
1878  lastRealRow = CPMaxRange(rowRange) - 1,
1879  rectOfLastRealRow = [self _rectOfRow:lastRealRow checkRange:NO],
1880  bottomOfRealRows = CGRectGetMaxY(rectOfLastRealRow),
1881  rectBottom = CGRectGetMaxY(aRect);
1882 
1883  // If the bottom of the last real row is at or below the bottom of aRect, we are done
1884  if (bottomOfRealRows >= rectBottom)
1885  return rowRange;
1886 
1887  var numberOfSynthesizedRows = CEIL((rectBottom - bottomOfRealRows) / FULL_ROW_HEIGHT());
1888 
1889  rowRange.length += numberOfSynthesizedRows;
1890 
1891  return rowRange;
1892 }
1893 
1899 - (CPIndexSet)columnIndexesInRect:(CGRect)aRect
1900 {
1901  // Complexity:
1902  // O(log numberOfColumns) if table view contains no hidden columns
1903  // O(numberOfColumns) if table view contains hidden columns
1904 
1905  var column = MAX(0, [self columnAtPoint:CGPointMake(aRect.origin.x, 0.0)]),
1906  lastColumn = [self columnAtPoint:CGPointMake(CGRectGetMaxX(aRect), 0.0)];
1907 
1908  if (lastColumn === CPNotFound)
1909  lastColumn = NUMBER_OF_COLUMNS() - 1;
1910 
1911  // Don't bother doing the expensive removal of hidden indexes if we have no hidden columns.
1912  if (_numberOfHiddenColumns <= 0)
1913  return [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(column, lastColumn - column + 1)];
1914 
1915  var indexSet = [CPIndexSet indexSet];
1916 
1917  for (; column <= lastColumn; ++column)
1918  {
1919  var tableColumn = _tableColumns[column];
1920 
1921  if (![tableColumn isHidden])
1922  [indexSet addIndex:column];
1923  }
1924 
1925  return indexSet;
1926 }
1927 
1933 - (CPInteger)columnAtPoint:(CGPoint)aPoint
1934 {
1935  // Complexity:
1936  // O(log numberOfColumns) if table view contains no hidden columns
1937  // O(numberOfColumns) if table view contains hidden columns
1938 
1939  var bounds = [self bounds];
1940 
1941  if (!CGRectContainsPoint(bounds, aPoint))
1942  return CPNotFound;
1943 
1945 
1946  var x = aPoint.x,
1947  low = 0,
1948  high = _tableColumnRanges.length - 1;
1949 
1950  while (low <= high)
1951  {
1952  var middle = FLOOR(low + (high - low) / 2),
1953  range = _tableColumnRanges[middle];
1954 
1955  if (x < range.location)
1956  high = middle - 1;
1957 
1958  else if (x >= CPMaxRange(range))
1959  low = middle + 1;
1960 
1961  else
1962  {
1963  var numberOfColumns = _tableColumnRanges.length;
1964 
1965  while (middle < numberOfColumns && [_tableColumns[middle] isHidden])
1966  ++middle;
1967 
1968  if (middle < numberOfColumns)
1969  return middle;
1970 
1971  return CPNotFound;
1972  }
1973  }
1974 
1975  return CPNotFound;
1976 }
1977 
1983 - (CPInteger)rowAtPoint:(CGPoint)aPoint
1984 {
1985  // Complexity:
1986  // O(1) for fixed height rows or point out of bounds
1987  // O(log numberOfRows) for variable height rows
1988 
1989  // aPoint.x must be within our bounds
1990  var bounds = [self bounds];
1991 
1992  if (aPoint.x < CGRectGetMinX(bounds) || aPoint.x >= CGRectGetMaxX(bounds))
1993  return -1;
1994 
1995  // aPoint.x is in bounds, now we just have to check aPoint.y
1996 
1998  {
1999  // First make sure aPoint.y is above the bottom of the last row, otherwise we might
2000  // search the (potentially large number of) rows for nothing.
2001  var heightInfo = [_cachedRowHeights lastObject];
2002 
2003  if (aPoint.y >= ROW_BOTTOM(heightInfo))
2004  return -1;
2005 
2006  return [_cachedRowHeights indexOfObject:aPoint
2007  inSortedRange:nil
2008  options:0
2009  usingComparator:function(aPoint, heightInfo)
2010  {
2011  if (aPoint.y < heightInfo.y)
2012  return CPOrderedAscending;
2013 
2014  if (aPoint.y > ROW_BOTTOM(heightInfo))
2015  return CPOrderedDescending;
2016 
2017  return CPOrderedSame;
2018  }];
2019  }
2020  else
2021  {
2022  var row = FLOOR(aPoint.y / FULL_ROW_HEIGHT());
2023 
2024  return row >= _numberOfRows ? -1 : row;
2025  }
2026 }
2027 
2036 - (CPInteger)rowForView:(CPView)aView
2037 {
2038  return [self rowNotColumn:YES forView:aView];
2039 }
2040 
2049 - (CPInteger)columnForView:(CPView)aView
2050 {
2051  return [self rowNotColumn:NO forView:aView];
2052 }
2053 
2057 - (CPInteger)rowNotColumn:(BOOL)isRow forView:(CPView)aView
2058 {
2059  if (![aView isKindOfClass:[CPView class]])
2060  return -1;
2061 
2062  var cellView = aView,
2063  contentView = [[self window] contentView],
2064  max_rec = 100;
2065 
2066  while (max_rec--)
2067  {
2068  if (!cellView || cellView === contentView)
2069  {
2070  return -1;
2071  }
2072  else
2073  {
2074  var superview = [cellView superview];
2075 
2076  if ([superview isKindOfClass:[CPTableView class]])
2077  {
2078  break;
2079  }
2080 
2081  cellView = superview;
2082  }
2083  }
2084 
2085  var exposedRows = [],
2086  exposedColumns = [];
2087 
2088  [_exposedRows getIndexes:exposedRows maxCount:-1 inIndexRange:nil];
2089  [_exposedColumns getIndexes:exposedColumns maxCount:-1 inIndexRange:nil];
2090 
2091  var colcount = exposedColumns.length,
2092  countOfRows = exposedRows.length;
2093 
2094  while (colcount--)
2095  {
2096  var column = exposedColumns[colcount],
2097  tableColumnUID = [_tableColumns[column] UID],
2098  dataViewsInTableColumn = _dataViewsForTableColumns[tableColumnUID],
2099  rowcount = countOfRows;
2100 
2101  while (rowcount--)
2102  {
2103  var row = exposedRows[rowcount];
2104 
2105  if (cellView == dataViewsInTableColumn[row])
2106  return isRow ? row : column;
2107  }
2108  }
2109 
2110  return -1;
2111 }
2112 
2120 - (CGRect)frameOfDataViewAtColumn:(CPInteger)aColumn row:(CPInteger)aRow
2121 {
2123 
2124  if (aColumn > [self numberOfColumns] || aRow > [self numberOfRows])
2125  return CGRectMakeZero();
2126 
2127  var tableColumnRange = _tableColumnRanges[aColumn],
2128  rectOfRow = [self rectOfRow:aRow],
2129  leftInset = FLOOR(_intercellSpacing.width / 2.0),
2130  topInset = FLOOR(_intercellSpacing.height / 2.0);
2131 
2132  return CGRectMake(tableColumnRange.location + leftInset, CGRectGetMinY(rectOfRow) + topInset, tableColumnRange.length - _intercellSpacing.width, CGRectGetHeight(rectOfRow) - _intercellSpacing.height);
2133 }
2134 
2138 - (void)resizeWithOldSuperviewSize:(CGSize)aSize
2139 {
2140  [super resizeWithOldSuperviewSize:aSize];
2141 
2142  if (_disableAutomaticResizing)
2143  return;
2144 
2145  var mask = _columnAutoResizingStyle;
2146 
2147  // should we actually do some resizing?
2148  if (!_lastColumnShouldSnap)
2149  {
2150  // did the clip view intersect the old tablesize?
2151  var superview = [self superview];
2152 
2153  if (!superview || ![superview isKindOfClass:[CPClipView class]])
2154  return;
2155 
2156  var superviewWidth = [superview bounds].size.width,
2157  lastColumnMaxX = CGRectGetMaxX([self rectOfColumn:[self numberOfColumns] -1]);
2158 
2159  // Fix me: this fires on the table setup at times
2160  if (lastColumnMaxX >= superviewWidth && lastColumnMaxX <= aSize.width || lastColumnMaxX <= superviewWidth && lastColumnMaxX >= aSize.width)
2161  _lastColumnShouldSnap = YES;
2162  else if (mask === CPTableViewUniformColumnAutoresizingStyle)
2163  return;
2164  }
2165 
2166  if (mask === CPTableViewUniformColumnAutoresizingStyle)
2167  [self _resizeAllColumnUniformlyWithOldSize:aSize];
2168  else if (mask === CPTableViewLastColumnOnlyAutoresizingStyle)
2169  [self sizeLastColumnToFit];
2170  else if (mask === CPTableViewFirstColumnOnlyAutoresizingStyle)
2171  [self _autoResizeFirstColumn];
2172 }
2173 
2177 - (void)_autoResizeFirstColumn
2178 {
2179  var superview = [self superview];
2180 
2181  if (!superview)
2182  return;
2183 
2185 
2186  var count = NUMBER_OF_COLUMNS(),
2187  columnToResize = nil,
2188  totalWidth = 0,
2189  i = 0;
2190 
2191  for (; i < count; i++)
2192  {
2193  var column = _tableColumns[i];
2194 
2195  if (![column isHidden])
2196  {
2197  if (!columnToResize)
2198  columnToResize = column;
2199  totalWidth += [column width] + _intercellSpacing.width;
2200  }
2201  }
2202 
2203  // If there is a visible column
2204  if (columnToResize)
2205  {
2206  var superviewSize = [superview bounds].size,
2207  newWidth = superviewSize.width - totalWidth;
2208 
2209  newWidth += [columnToResize width];
2210  [columnToResize _tryToResizeToWidth:newWidth];
2211  }
2212 
2213  [self setNeedsLayout];
2214 }
2215 
2216 
2221 - (void)_resizeAllColumnUniformlyWithOldSize:(CGSize)oldSize
2222 {
2223  // what we care about is the superview clip rect
2224  // FIX ME: if it's not in a scrollview this doesn't really work
2225  var superview = [self superview];
2226 
2227  if (!superview || ![superview isKindOfClass:[CPClipView class]])
2228  return;
2229 
2231 
2232  var superviewWidth = [superview bounds].size.width,
2233  count = NUMBER_OF_COLUMNS(),
2234  resizableColumns = [CPIndexSet indexSet],
2235  remainingSpace = 0.0,
2236  i = 0;
2237 
2238  // find resizable columns
2239  // FIX ME: we could cache resizableColumns after this loop and reuse it during the resize
2240  for (; i < count; i++)
2241  {
2242  var tableColumn = _tableColumns[i];
2243  if (![tableColumn isHidden] && ([tableColumn resizingMask] & CPTableColumnAutoresizingMask))
2244  [resizableColumns addIndex:i];
2245  }
2246 
2247  var maxXofColumns = CGRectGetMaxX([self rectOfColumn:[resizableColumns lastIndex]]),
2248  remainingSpace = superviewWidth - maxXofColumns,
2249  resizeableColumnsCount = [resizableColumns count],
2250  proportionate = 0;
2251 
2252  while (remainingSpace && resizeableColumnsCount)
2253  {
2254  // Divy out the space.
2255  proportionate += remainingSpace / resizeableColumnsCount;
2256 
2257  // Reset the remaining space to 0
2258  remainingSpace = 0.0;
2259 
2260  var index = CPNotFound;
2261 
2262  while ((index = [resizableColumns indexGreaterThanIndex:index]) !== CPNotFound)
2263  {
2264  var item = _tableColumns[index],
2265  proposedWidth = [item width] + proportionate,
2266  resizeLeftovers = [item _tryToResizeToWidth:proposedWidth];
2267 
2268  if (resizeLeftovers)
2269  {
2270  [resizableColumns removeIndex:index];
2271 
2272  remainingSpace += resizeLeftovers;
2273  }
2274  }
2275  }
2276 
2277  // now that we've reached the end we know there are likely rounding errors
2278  // so we should size the last resized to fit
2279 
2280  // find the last visisble column
2281  while (count-- && [_tableColumns[count] isHidden]);
2282 
2283  // find the max x, but subtract a single pixel since the spacing isn't applicable here.
2284  var delta = superviewWidth - CGRectGetMaxX([self rectOfColumn:count]) - ([self intercellSpacing].width || 1),
2285  newSize = [item width] + delta;
2286 
2287  [item _tryToResizeToWidth:newSize];
2288 }
2289 
2301 - (void)setColumnAutoresizingStyle:(unsigned)style
2302 {
2303  //FIX ME: CPTableViewSequentialColumnAutoresizingStyle and CPTableViewReverseSequentialColumnAutoresizingStyle are not yet implemented
2304  _columnAutoResizingStyle = style;
2305 }
2306 
2310 - (unsigned)columnAutoresizingStyle
2311 {
2312  return _columnAutoResizingStyle;
2313 }
2314 
2318 - (void)sizeLastColumnToFit
2319 {
2320  _lastColumnShouldSnap = YES;
2321 
2322  var superview = [self superview];
2323 
2324  if (!superview)
2325  return;
2326 
2327  var superviewSize = [superview bounds].size;
2328 
2330 
2331  var count = NUMBER_OF_COLUMNS();
2332 
2333  // Decrement the counter until we get to the last column that's not hidden
2334  while (count-- && [_tableColumns[count] isHidden]);
2335 
2336  // If the last column exists
2337  if (count >= 0)
2338  {
2339  var columnToResize = _tableColumns[count],
2340  newSize = MAX(0.0, superviewSize.width - CGRectGetMinX([self rectOfColumn:count]) - _intercellSpacing.width);
2341 
2342  [columnToResize _tryToResizeToWidth:newSize];
2343  }
2344 
2345  [self setNeedsLayout];
2346 }
2347 
2351 - (void)noteNumberOfRowsChanged
2352 {
2353  var oldNumberOfRows = _numberOfRows;
2354 
2355  _numberOfRows = nil;
2356  _cachedRowHeights = [];
2357 
2358  // this line serves two purposes
2359  // 1. it updates the _numberOfRows cache with the -numberOfRows call
2360  // 2. it updates the row height cache if needed
2361  [self noteHeightOfRowsWithIndexesChanged:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, [self numberOfRows])]];
2362 
2363  // remove row indexes from the selection if they no longer exist
2364  var hangingSelections = oldNumberOfRows - _numberOfRows;
2365 
2366  if (hangingSelections > 0)
2367  {
2368  // For optimal performance, only send a notification if indices were actually removed.
2369  var previousSelectionCount = [_selectedRowIndexes count];
2370 
2371  [_selectedRowIndexes removeIndexesInRange:CPMakeRange(_numberOfRows, hangingSelections)];
2372 
2373  if (![_selectedRowIndexes containsIndex:[self selectedRow]])
2374  _lastSelectedRow = CPNotFound;
2375 
2376  if (previousSelectionCount > [_selectedRowIndexes count])
2377  [self _noteSelectionDidChange];
2378  }
2379 
2380  [self tile];
2381 }
2382 
2383 /*
2384  Populates the row height cache if necessary.
2385 */
2386 - (void)_populateRowHeightCacheIfNeeded
2387 {
2388  if ([self numberOfRows] !== _cachedRowHeights.length)
2389  [self noteHeightOfRowsWithIndexesChanged:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, _numberOfRows)]];
2390 }
2391 
2397 - (void)noteHeightOfRowsWithIndexesChanged:(CPIndexSet)anIndexSet
2398 {
2399  if (!HAS_VARIABLE_ROW_HEIGHTS())
2400  return;
2401 
2402  // Update the height of the given rows by calling the delegate. Since the row height cache also contains
2403  // y coordinates, we have to update the y coordinates of all rows below the first valid row in the range.
2404  var i = [anIndexSet indexGreaterThanOrEqualToIndex:0];
2405 
2406  if (i === CPNotFound)
2407  return;
2408 
2409  var y = i < _cachedRowHeights.length ? _cachedRowHeights[i].y : 0;
2410 
2411  for (var count = [self numberOfRows]; i < count; ++i)
2412  {
2413  var height;
2414 
2415  if ([anIndexSet containsIndex:i])
2416  height = [self _sendDelegateHeightOfRow:i];
2417  else
2418  height = _cachedRowHeights[i].height || _rowHeight; // in case the cache entry is empty
2419 
2420  _cachedRowHeights[i] = {y:y, height:height};
2421  y += height + _intercellSpacing.height;
2422  }
2423 }
2424 
2428 - (void)tile
2429 {
2431 
2432  var width = _tableColumnRanges.length > 0 ? CPMaxRange([_tableColumnRanges lastObject]) : 0.0,
2433  superview = [self superview],
2434  height = 0;
2435 
2436  if (!HAS_VARIABLE_ROW_HEIGHTS())
2437  height = FULL_ROW_HEIGHT() * _numberOfRows;
2438  else if (_numberOfRows > 0)
2439  {
2440  [self _populateRowHeightCacheIfNeeded];
2441 
2442  var heightInfo = _cachedRowHeights[_cachedRowHeights.length - 1];
2443 
2444  height = ROW_BOTTOM(heightInfo);
2445  }
2446 
2447  if ([superview isKindOfClass:[CPClipView class]])
2448  {
2449  var superviewSize = [superview bounds].size;
2450 
2451  width = MAX(superviewSize.width, width);
2452  height = MAX(superviewSize.height, height);
2453  }
2454 
2455  [self setFrameSize:CGSizeMake(width, height)];
2456 
2457  [self setNeedsLayout];
2458  [self setNeedsDisplay:YES];
2459 }
2460 
2461 
2467 - (void)scrollRowToVisible:(int)rowIndex
2468 {
2469  var visible = [self visibleRect],
2470  rowRect = [self rectOfRow:rowIndex];
2471 
2472  visible.origin.y = rowRect.origin.y;
2473  visible.size.height = rowRect.size.height;
2474 
2475  [self scrollRectToVisible:visible];
2476 }
2477 
2483 - (void)scrollColumnToVisible:(int)columnIndex
2484 {
2485  var visible = [self visibleRect],
2486  colRect = [self rectOfColumn:columnIndex];
2487 
2488  visible.origin.x = colRect.origin.x;
2489  visible.size.width = colRect.size.width;
2490 
2491  [self scrollRectToVisible:visible];
2492  [_headerView scrollRectToVisible:colRect];
2493 }
2494 
2501 - (void)setAutosaveName:(CPString)theAutosaveName
2502 {
2503  if (_autosaveName === theAutosaveName)
2504  return;
2505 
2506  _autosaveName = theAutosaveName;
2507 
2508  [self setAutosaveTableColumns:!!theAutosaveName];
2509  [self _restoreFromAutosave];
2510 }
2511 
2515 - (CPString)autosaveName
2516 {
2517  return _autosaveName;
2518 }
2519 
2526 - (void)setAutosaveTableColumns:(BOOL)shouldAutosave
2527 {
2528  _autosaveTableColumns = shouldAutosave;
2529 }
2530 
2534 - (BOOL)autosaveTableColumns
2535 {
2536  return _autosaveTableColumns;
2537 }
2538 
2542 - (CPString)_columnsKeyForAutosaveName:(CPString)theAutosaveName
2543 {
2544  return @"CPTableView Columns " + theAutosaveName;
2545 }
2546 
2550 - (BOOL)_autosaveEnabled
2551 {
2552  return [self autosaveName] && [self autosaveTableColumns];
2553 }
2554 
2561 - (void)_autosave
2562 {
2563  if (![self _autosaveEnabled])
2564  return;
2565 
2566  var userDefaults = [CPUserDefaults standardUserDefaults],
2567  autosaveName = [self autosaveName];
2568 
2569  var columns = [self tableColumns],
2570  columnsSetup = [];
2571 
2572  for (var i = 0; i < [columns count]; i++)
2573  {
2574  var column = [columns objectAtIndex:i],
2575  metaData = @{
2576  @"identifier": [column identifier],
2577  @"width": [column width]
2578  };
2579 
2580  [columnsSetup addObject:metaData];
2581  }
2582 
2583  [userDefaults setObject:columnsSetup forKey:[self _columnsKeyForAutosaveName:autosaveName]];
2584 }
2585 
2589 - (void)_restoreFromAutosave
2590 {
2591  if (![self _autosaveEnabled])
2592  return;
2593 
2594  var userDefaults = [CPUserDefaults standardUserDefaults],
2595  autosaveName = [self autosaveName],
2596  tableColumns = [userDefaults objectForKey:[self _columnsKeyForAutosaveName:autosaveName]];
2597 
2598  if ([tableColumns count] != [[self tableColumns] count])
2599  return;
2600 
2601  for (var i = 0; i < [tableColumns count]; i++)
2602  {
2603  var metaData = [tableColumns objectAtIndex:i],
2604  columnIdentifier = [metaData objectForKey:@"identifier"],
2605  column = [self columnWithIdentifier:columnIdentifier],
2606  tableColumn = [self tableColumnWithIdentifier:columnIdentifier];
2607 
2608  if (tableColumn && column != CPNotFound)
2609  {
2610  [self _moveColumn:column toColumn:i];
2611  [tableColumn setWidth:[metaData objectForKey:@"width"]];
2612  }
2613  }
2614 }
2615 
2763 - (void)setDelegate:(id)aDelegate
2764 {
2765  if (_delegate === aDelegate)
2766  return;
2767 
2768  var defaultCenter = [CPNotificationCenter defaultCenter];
2769 
2770  if (_delegate)
2771  {
2772  if ([_delegate respondsToSelector:@selector(tableViewColumnDidMove:)])
2773  [defaultCenter
2774  removeObserver:_delegate
2775  name:CPTableViewColumnDidMoveNotification
2776  object:self];
2777 
2778  if ([_delegate respondsToSelector:@selector(tableViewColumnDidResize:)])
2779  [defaultCenter
2780  removeObserver:_delegate
2781  name:CPTableViewColumnDidResizeNotification
2782  object:self];
2783 
2784  if ([_delegate respondsToSelector:@selector(tableViewSelectionDidChange:)])
2785  [defaultCenter
2786  removeObserver:_delegate
2787  name:CPTableViewSelectionDidChangeNotification
2788  object:self];
2789 
2790  if ([_delegate respondsToSelector:@selector(tableViewSelectionIsChanging:)])
2791  [defaultCenter
2792  removeObserver:_delegate
2793  name:CPTableViewSelectionIsChangingNotification
2794  object:self];
2795  }
2796 
2797  _delegate = aDelegate;
2798  _implementedDelegateMethods = 0;
2799 
2800  if ([_delegate respondsToSelector:@selector(selectionShouldChangeInTableView:)])
2801  _implementedDelegateMethods |= CPTableViewDelegate_selectionShouldChangeInTableView_;
2802 
2803  if ([_delegate respondsToSelector:@selector(tableView:viewForTableColumn:row:)])
2804  _implementedDelegateMethods |= CPTableViewDelegate_tableView_viewForTableColumn_row_;
2805  else if ([_delegate respondsToSelector:@selector(tableView:dataViewForTableColumn:row:)])
2806  {
2807  _implementedDelegateMethods |= CPTableViewDelegate_tableView_dataViewForTableColumn_row_;
2808  CPLog.warn("tableView:dataViewForTableColumn: is deprecated. You should use -tableView:viewForTableColumn: where you can request the view with -makeViewWithIdentifier:owner:");
2809  }
2810 
2811  [self _updateIsViewBased];
2812 
2813  if ([_delegate respondsToSelector:@selector(tableView:didClickTableColumn:)])
2814  _implementedDelegateMethods |= CPTableViewDelegate_tableView_didClickTableColumn_;
2815 
2816  if ([_delegate respondsToSelector:@selector(tableView:didDragTableColumn:)])
2817  _implementedDelegateMethods |= CPTableViewDelegate_tableView_didDragTableColumn_;
2818 
2819  if ([_delegate respondsToSelector:@selector(tableView:heightOfRow:)])
2820  _implementedDelegateMethods |= CPTableViewDelegate_tableView_heightOfRow_;
2821 
2822  if ([_delegate respondsToSelector:@selector(tableView:isGroupRow:)])
2823  _implementedDelegateMethods |= CPTableViewDelegate_tableView_isGroupRow_;
2824 
2825  if ([_delegate respondsToSelector:@selector(tableView:mouseDownInHeaderOfTableColumn:)])
2826  _implementedDelegateMethods |= CPTableViewDelegate_tableView_mouseDownInHeaderOfTableColumn_;
2827 
2828  if ([_delegate respondsToSelector:@selector(tableView:nextTypeSelectMatchFromRow:toRow:forString:)])
2829  _implementedDelegateMethods |= CPTableViewDelegate_tableView_nextTypeSelectMatchFromRow_toRow_forString_;
2830 
2831  if ([_delegate respondsToSelector:@selector(tableView:selectionIndexesForProposedSelection:)])
2832  _implementedDelegateMethods |= CPTableViewDelegate_tableView_selectionIndexesForProposedSelection_;
2833 
2834  if ([_delegate respondsToSelector:@selector(tableView:shouldEditTableColumn:row:)])
2835  _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldEditTableColumn_row_;
2836 
2837  if ([_delegate respondsToSelector:@selector(tableView:shouldSelectRow:)])
2838  _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldSelectRow_;
2839 
2840  if ([_delegate respondsToSelector:@selector(tableView:shouldSelectTableColumn:)])
2841  _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldSelectTableColumn_;
2842 
2843  if ([_delegate respondsToSelector:@selector(tableView:shouldShowViewExpansionForTableColumn:row:)])
2844  _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldShowViewExpansionForTableColumn_row_;
2845 
2846  if ([_delegate respondsToSelector:@selector(tableView:shouldTrackView:forTableColumn:row:)])
2847  _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldTrackView_forTableColumn_row_;
2848 
2849  if ([_delegate respondsToSelector:@selector(tableView:shouldTypeSelectForEvent:withCurrentSearchString:)])
2850  _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldTypeSelectForEvent_withCurrentSearchString_;
2851 
2852  if ([_delegate respondsToSelector:@selector(tableView:toolTipForView:rect:tableColumn:row:mouseLocation:)])
2853  _implementedDelegateMethods |= CPTableViewDelegate_tableView_toolTipForView_rect_tableColumn_row_mouseLocation_;
2854 
2855  if ([_delegate respondsToSelector:@selector(tableView:typeSelectStringForTableColumn:row:)])
2856  _implementedDelegateMethods |= CPTableViewDelegate_tableView_typeSelectStringForTableColumn_row_;
2857 
2858  if ([_delegate respondsToSelector:@selector(tableView:willDisplayView:forTableColumn:row:)])
2859  _implementedDelegateMethods |= CPTableViewDelegate_tableView_willDisplayView_forTableColumn_row_;
2860 
2861  if ([_delegate respondsToSelector:@selector(tableView:menuForTableColumn:row:)])
2862  _implementedDelegateMethods |= CPTableViewDelegate_tableViewMenuForTableColumn_row_;
2863 
2864  if ([_delegate respondsToSelector:@selector(tableView:shouldReorderColumn:toColumn:)])
2865  _implementedDelegateMethods |= CPTableViewDelegate_tableView_shouldReorderColumn_toColumn_;
2866 
2867  if ([_delegate respondsToSelector:@selector(tableViewColumnDidMove:)])
2868  [defaultCenter
2869  addObserver:_delegate
2870  selector:@selector(tableViewColumnDidMove:)
2871  name:CPTableViewColumnDidMoveNotification
2872  object:self];
2873 
2874  if ([_delegate respondsToSelector:@selector(tableViewColumnDidResize:)])
2875  [defaultCenter
2876  addObserver:_delegate
2877  selector:@selector(tableViewColumnDidResize:)
2878  name:CPTableViewColumnDidResizeNotification
2879  object:self];
2880 
2881  if ([_delegate respondsToSelector:@selector(tableViewSelectionDidChange:)])
2882  [defaultCenter
2883  addObserver:_delegate
2884  selector:@selector(tableViewSelectionDidChange:)
2885  name:CPTableViewSelectionDidChangeNotification
2886  object:self];
2887 
2888  if ([_delegate respondsToSelector:@selector(tableViewSelectionIsChanging:)])
2889  [defaultCenter
2890  addObserver:_delegate
2891  selector:@selector(tableViewSelectionIsChanging:)
2892  name:CPTableViewSelectionIsChangingNotification
2893  object:self];
2894 }
2895 
2899 - (id)delegate
2900 {
2901  return _delegate;
2902 }
2903 
2907 - (void)_didClickTableColumn:(CPInteger)clickedColumn modifierFlags:(unsigned)modifierFlags
2908 {
2909  [self _changeSortDescriptorsForClickOnColumn:clickedColumn];
2910 
2911  if (_allowsColumnSelection)
2912  {
2913  if ([self _sendDelegateSelectionShouldChangeInTableView] && [self _sendDelegateShouldSelectTableColumn:clickedColumn])
2914  {
2915  [self _noteSelectionIsChanging];
2916  if (modifierFlags & CPPlatformActionKeyMask)
2917  {
2918  if ([self isColumnSelected:clickedColumn])
2919  [self deselectColumn:clickedColumn];
2920  else if ([self allowsMultipleSelection] == YES)
2921  [self selectColumnIndexes:[CPIndexSet indexSetWithIndex:clickedColumn] byExtendingSelection:YES];
2922 
2923  return;
2924  }
2925  else if (modifierFlags & CPShiftKeyMask)
2926  {
2927  // should be from clickedColumn to lastClickedColum with extending:(direction == previous selection)
2928  var startColumn = MIN(clickedColumn, [_selectedColumnIndexes lastIndex]),
2929  endColumn = MAX(clickedColumn, [_selectedColumnIndexes firstIndex]);
2930 
2931  [self selectColumnIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(startColumn, endColumn - startColumn + 1)]
2932  byExtendingSelection:YES];
2933 
2934  return;
2935  }
2936  else
2937  [self selectColumnIndexes:[CPIndexSet indexSetWithIndex:clickedColumn] byExtendingSelection:NO];
2938  }
2939  }
2940 
2941  [self _sendDelegateDidClickTableColumn:clickedColumn];
2942 }
2943 
2944 // From GNUSTEP
2948 - (void)_changeSortDescriptorsForClickOnColumn:(CPInteger)column
2949 {
2950  var tableColumn = [_tableColumns objectAtIndex:column],
2951  newMainSortDescriptor = [tableColumn sortDescriptorPrototype];
2952 
2953  if (!newMainSortDescriptor)
2954  return;
2955 
2956  var oldMainSortDescriptor = nil,
2957  oldSortDescriptors = [self sortDescriptors],
2958  newSortDescriptors = [CPArray arrayWithArray:oldSortDescriptors],
2959 
2960  e = [newSortDescriptors objectEnumerator],
2961  descriptor = nil,
2962  outdatedDescriptors = [CPArray array];
2963 
2964  if ([_sortDescriptors count] > 0)
2965  oldMainSortDescriptor = [[self sortDescriptors] objectAtIndex: 0];
2966 
2967  // Remove every main descriptor equivalents (normally only one)
2968  while ((descriptor = [e nextObject]) !== nil)
2969  {
2970  if ([[descriptor key] isEqual: [newMainSortDescriptor key]])
2971  [outdatedDescriptors addObject:descriptor];
2972  }
2973 
2974  // Invert the sort direction when the same column header is clicked twice
2975  if ([[newMainSortDescriptor key] isEqual:[oldMainSortDescriptor key]])
2976  newMainSortDescriptor = [oldMainSortDescriptor reversedSortDescriptor];
2977 
2978  [newSortDescriptors removeObjectsInArray:outdatedDescriptors];
2979  [newSortDescriptors insertObject:newMainSortDescriptor atIndex:0];
2980 
2981  [self setHighlightedTableColumn:tableColumn];
2982  [self setSortDescriptors:newSortDescriptors];
2983 }
2984 
2993 - (void)setIndicatorImage:(CPImage)anImage inTableColumn:(CPTableColumn)aTableColumn
2994 {
2995  if (aTableColumn)
2996  {
2997  var headerView = [aTableColumn headerView];
2998  if ([headerView respondsToSelector:@selector(_setIndicatorImage:)])
2999  [headerView _setIndicatorImage:anImage];
3000  }
3001 }
3002 
3006 - (CPImage)_tableHeaderSortImage
3007 {
3008  return [self currentValueForThemeAttribute:@"sort-image"];
3009 }
3010 
3014 - (CPImage)_tableHeaderReverseSortImage
3015 {
3016  return [self currentValueForThemeAttribute:@"sort-image-reversed"];
3017 }
3018 
3022 - (CPTableColumn)highlightedTableColumn
3023 {
3024  return _currentHighlightedTableColumn;
3025 }
3026 
3030 - (void)setHighlightedTableColumn:(CPTableColumn)aTableColumn
3031 {
3032  if (_currentHighlightedTableColumn == aTableColumn)
3033  return;
3034 
3035  if (_headerView)
3036  {
3037  if (_currentHighlightedTableColumn != nil)
3038  [[_currentHighlightedTableColumn headerView] unsetThemeState:CPThemeStateSelected];
3039 
3040  if (aTableColumn != nil)
3041  [[aTableColumn headerView] setThemeState:CPThemeStateSelected];
3042  }
3043 
3044  _currentHighlightedTableColumn = aTableColumn;
3045 }
3046 
3053 - (BOOL)canDragRowsWithIndexes:(CPIndexSet)rowIndexes atPoint:(CGPoint)mouseDownPoint
3054 {
3055  return [rowIndexes count] > 0 && [self numberOfRows] > 0 && [self numberOfColumns] > 0;
3056 }
3057 
3068 - (CPImage)dragImageForRowsWithIndexes:(CPIndexSet)dragRows tableColumns:(CPArray)theTableColumns event:(CPEvent)dragEvent offset:(CGPoint)dragImageOffset
3069 {
3070  return [self valueForThemeAttribute:@"image-generic-file"];
3071 }
3072 
3085 - (CPView)dragViewForRowsWithIndexes:(CPIndexSet)theDraggedRows tableColumns:(CPArray)theTableColumns event:(CPEvent)theDragEvent offset:(CGPoint)dragViewOffset
3086 {
3087  var bounds = [self bounds],
3088  view = [[CPView alloc] initWithFrame:bounds];
3089 
3090  [view setAlphaValue:0.7];
3091 
3092  // We have to fetch all the data views for the selected rows and columns
3093  // After that we can copy these add them to a transparent drag view and use that drag view
3094  // to make it appear we are dragging images of those rows (as you would do in regular Cocoa)
3095  var columnIndex = [theTableColumns count];
3096  while (columnIndex--)
3097  {
3098  var tableColumn = [theTableColumns objectAtIndex:columnIndex],
3099  row = [theDraggedRows firstIndex];
3100 
3101  while (row !== CPNotFound)
3102  {
3103  var dataView = [self _newDataViewForRow:row tableColumn:tableColumn];
3104 
3105  [dataView setFrame:[self frameOfDataViewAtColumn:columnIndex row:row]];
3106 
3107  [self _setObjectValueForTableColumn:tableColumn row:row forView:dataView];
3108  [view addSubview:dataView];
3109  [_draggingViews addObject:dataView];
3110 
3111  row = [theDraggedRows indexGreaterThanIndex:row];
3112  }
3113  }
3114 
3115  var dragPoint = [self convertPoint:[theDragEvent locationInWindow] fromView:nil];
3116  dragViewOffset.x = CGRectGetWidth(bounds) / 2 - dragPoint.x;
3117  dragViewOffset.y = CGRectGetHeight(bounds) / 2 - dragPoint.y;
3118 
3119  return view;
3120 }
3121 
3128 - (CPView)_dragViewForColumn:(CPInteger)theColumnIndex event:(CPEvent)theDragEvent offset:(CGPoint)theDragViewOffset
3129 {
3130  var dragView = [[_CPColumnDragView alloc] initWithLineColor:[self gridColor]],
3131  tableColumn = [[self tableColumns] objectAtIndex:theColumnIndex],
3132  defaultRowHeight = [self valueForThemeAttribute:@"default-row-height"],
3133  bounds = CGRectMake(0.0, 0.0, [tableColumn width], CGRectGetHeight([self exposedRect]) + defaultRowHeight),
3134  columnRect = [self rectOfColumn:theColumnIndex],
3135  headerView = [tableColumn headerView],
3136  row = [_exposedRows firstIndex];
3137 
3138  [dragView setFrame:bounds];
3139 
3140  while (row !== CPNotFound)
3141  {
3142  var dataView = [self _newDataViewForRow:row tableColumn:tableColumn],
3143  dataViewFrame = [self frameOfDataViewAtColumn:theColumnIndex row:row];
3144 
3145  // Only one column is ever dragged so we just place the view at
3146  dataViewFrame.origin.x = 0.0;
3147 
3148  // Offset by table header height - scroll position
3149  dataViewFrame.origin.y = ( CGRectGetMinY(dataViewFrame) - CGRectGetMinY([self exposedRect]) ) + defaultRowHeight;
3150  [dataView setFrame:dataViewFrame];
3151 
3152  [self _setObjectValueForTableColumn:tableColumn row:row forView:dataView];
3153  [dragView addSubview:dataView];
3154  [_draggingViews addObject:dataView];
3155 
3156  row = [_exposedRows indexGreaterThanIndex:row];
3157  }
3158 
3159  // Add a copy of the header view.
3161  [dragView addSubview:columnHeaderView];
3162 
3163  [dragView setBackgroundColor:[CPColor whiteColor]];
3164  [dragView setAlphaValue:0.7];
3165 
3166  return dragView;
3167 }
3168 
3173 - (void)setDraggingSourceOperationMask:(CPDragOperation)mask forLocal:(BOOL)isLocal
3174 {
3175  //ignore local for the time being since only one capp app can run at a time...
3176  _dragOperationDefaultMask = mask;
3177 }
3178 
3184 - (void)setDropRow:(CPInteger)row dropOperation:(CPTableViewDropOperation)operation
3185 {
3186  if (row > [self numberOfRows] && operation === CPTableViewDropOn)
3187  {
3188  var numberOfRows = [self numberOfRows] + 1,
3189  reason = @"Attempt to set dropRow=" + row +
3190  " dropOperation=CPTableViewDropOn when [0 - " + numberOfRows + "] is valid range of rows.";
3191 
3192  [[CPException exceptionWithName:@"Error" reason:reason userInfo:nil] raise];
3193  }
3194 
3195  _retargetedDropRow = row;
3196  _retargetedDropOperation = operation;
3197 }
3198 
3210 - (void)setDraggingDestinationFeedbackStyle:(CPTableViewDraggingDestinationFeedbackStyle)aStyle
3211 {
3212  //FIX ME: this should vary up the highlight color, currently nothing is being done with it
3213  _destinationDragStyle = aStyle;
3214 }
3215 
3226 - (CPTableViewDraggingDestinationFeedbackStyle)draggingDestinationFeedbackStyle
3227 {
3228  return _destinationDragStyle;
3229 }
3230 
3236 - (void)setVerticalMotionCanBeginDrag:(BOOL)aFlag
3237 {
3238  _verticalMotionCanDrag = aFlag;
3239 }
3240 
3244 - (BOOL)verticalMotionCanBeginDrag
3245 {
3246  return _verticalMotionCanDrag;
3247 }
3248 
3249 - (CPTableColumn)_tableColumnForSortDescriptor:(CPSortDescriptor)theSortDescriptor
3250 {
3251  var tableColumns = [self tableColumns];
3252 
3253  for (var i = 0; i < [tableColumns count]; i++)
3254  {
3255  var tableColumn = [tableColumns objectAtIndex:i],
3256  sortDescriptorPrototype = [tableColumn sortDescriptorPrototype];
3257 
3258  if (!sortDescriptorPrototype)
3259  continue;
3260 
3261  if ([sortDescriptorPrototype key] === [theSortDescriptor key]
3262  && [sortDescriptorPrototype selector] === [theSortDescriptor selector])
3263  {
3264  return tableColumn;
3265  }
3266  }
3267 
3268  return nil;
3269 }
3270 
3276 - (void)setSortDescriptors:(CPArray)sortDescriptors
3277 {
3278  var oldSortDescriptors = [[self sortDescriptors] copy],
3279  newSortDescriptors = [CPArray array];
3280 
3281  if (sortDescriptors !== nil)
3282  [newSortDescriptors addObjectsFromArray:sortDescriptors];
3283 
3284  if ([newSortDescriptors isEqual:oldSortDescriptors])
3285  return;
3286 
3287  _sortDescriptors = newSortDescriptors;
3288 
3289  var oldColumn = nil,
3290  newColumn = nil;
3291 
3292  if ([newSortDescriptors count] > 0)
3293  {
3294  var newMainSortDescriptor = [newSortDescriptors objectAtIndex:0];
3295  newColumn = [self _tableColumnForSortDescriptor:newMainSortDescriptor];
3296  }
3297 
3298  if ([oldSortDescriptors count] > 0)
3299  {
3300  var oldMainSortDescriptor = [oldSortDescriptors objectAtIndex:0];
3301  oldColumn = [self _tableColumnForSortDescriptor:oldMainSortDescriptor];
3302  }
3303 
3304  var image = [newMainSortDescriptor ascending] ? [self _tableHeaderSortImage] : [self _tableHeaderReverseSortImage];
3305  [self setIndicatorImage:nil inTableColumn:oldColumn];
3306  [self setIndicatorImage:image inTableColumn:newColumn];
3307 
3308  [self _sendDataSourceSortDescriptorsDidChange:oldSortDescriptors];
3309 
3310  var binderClass = [[self class] _binderClassForBinding:@"sortDescriptors"];
3311  [[binderClass getBinding:@"sortDescriptors" forObject:self] reverseSetValueFor:@"sortDescriptors"];
3312 }
3313 
3317 - (CPArray)sortDescriptors
3318 {
3319  return _sortDescriptors;
3320 }
3321 
3325 - (id)_objectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
3326 {
3327  var tableColumnUID = [aTableColumn UID],
3328  tableColumnObjectValues = _objectValues[tableColumnUID];
3329 
3330  if (!tableColumnObjectValues)
3331  {
3332  tableColumnObjectValues = [];
3333  _objectValues[tableColumnUID] = tableColumnObjectValues;
3334  }
3335 
3336  var objectValue = tableColumnObjectValues[aRowIndex];
3337 
3338  // tableView:objectValueForTableColumn:row: is optional if content bindings are in place.
3339  if (objectValue === undefined)
3340  {
3341  if ([self _dataSourceRespondsToObjectValueForTableColumn])
3342  {
3343  objectValue = [self _sendDataSourceObjectValueForTableColumn:aTableColumn row:aRowIndex];
3344  tableColumnObjectValues[aRowIndex] = objectValue;
3345  }
3346  else if (!_isViewBased && ![self infoForBinding:@"content"])
3347  {
3348  CPLog.warn(@"no content binding established and data source " + [_dataSource description] + " does not implement tableView:objectValueForTableColumn:row:");
3349  }
3350  }
3351 
3352  return objectValue;
3353 }
3354 
3355 
3359 - (CGRect)exposedRect
3360 {
3361  if (!_exposedRect)
3362  {
3363  var superview = [self superview];
3364 
3365  // FIXME: Should we be rect intersecting in case
3366  // there are multiple views in the clip view?
3367  if ([superview isKindOfClass:[CPClipView class]])
3368  _exposedRect = [superview bounds];
3369  else
3370  _exposedRect = [self bounds];
3371  }
3372 
3373  return _exposedRect;
3374 }
3375 
3379 - (void)load
3380 {
3381  if (_reloadAllRows)
3382  {
3383  [self _unloadDataViewsInRows:_exposedRows columns:_exposedColumns];
3384 
3385  _exposedRows = [CPIndexSet indexSet];
3386  _exposedColumns = [CPIndexSet indexSet];
3387 
3388  _reloadAllRows = NO;
3389  }
3390 
3391  var exposedRect = [self exposedRect],
3392  exposedRows = [CPIndexSet indexSetWithIndexesInRange:[self rowsInRect:exposedRect]],
3393  exposedColumns = [self columnIndexesInRect:exposedRect],
3394  obscuredRows = [_exposedRows copy],
3395  obscuredColumns = [_exposedColumns copy];
3396 
3397  [obscuredRows removeIndexes:exposedRows];
3398  [obscuredColumns removeIndexes:exposedColumns];
3399 
3400  var newlyExposedRows = [exposedRows copy],
3401  newlyExposedColumns = [exposedColumns copy];
3402 
3403  [newlyExposedRows removeIndexes:_exposedRows];
3404  [newlyExposedColumns removeIndexes:_exposedColumns];
3405 
3406  var previouslyExposedRows = [exposedRows copy],
3407  previouslyExposedColumns = [exposedColumns copy];
3408 
3409  [previouslyExposedRows removeIndexes:newlyExposedRows];
3410  [previouslyExposedColumns removeIndexes:newlyExposedColumns];
3411 
3412  [self _unloadDataViewsInRows:previouslyExposedRows columns:obscuredColumns];
3413  [self _unloadDataViewsInRows:obscuredRows columns:previouslyExposedColumns];
3414  [self _unloadDataViewsInRows:obscuredRows columns:obscuredColumns];
3415  [self _unloadDataViewsInRows:newlyExposedRows columns:newlyExposedColumns];
3416 
3417  [self _loadDataViewsInRows:previouslyExposedRows columns:newlyExposedColumns];
3418  [self _loadDataViewsInRows:newlyExposedRows columns:previouslyExposedColumns];
3419  [self _loadDataViewsInRows:newlyExposedRows columns:newlyExposedColumns];
3420 
3421  _exposedRows = exposedRows;
3422  _exposedColumns = exposedColumns;
3423 
3424  [_tableDrawView setFrame:exposedRect];
3425 
3426  [self setNeedsDisplay:YES];
3427 
3428  // if we have any columns to remove do that here
3429  if ([_differedColumnDataToRemove count])
3430  {
3431  for (var i = 0; i < _differedColumnDataToRemove.length; i++)
3432  {
3433  var data = _differedColumnDataToRemove[i],
3434  column = data.column,
3435  tableColumnUID = [column UID],
3436  dataViews = _dataViewsForTableColumns[tableColumnUID];
3437 
3438  for (var j = 0; j < [dataViews count]; j++)
3439  {
3440  [self _enqueueReusableDataView:[dataViews objectAtIndex:j]];
3441  }
3442  }
3443  [_differedColumnDataToRemove removeAllObjects];
3444  }
3445 
3446  // Now clear all the leftovers
3447  // FIXME: this could be faster!
3448  for (var identifier in _cachedDataViews)
3449  {
3450  var dataViews = _cachedDataViews[identifier],
3451  count = dataViews.length;
3452 
3453  while (count--)
3454  [dataViews[count] removeFromSuperview];
3455  }
3456 }
3457 
3461 - (void)_unloadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
3462 {
3463  if (![rows count] || ![columns count])
3464  return;
3465 
3466  var rowArray = [],
3467  columnArray = [];
3468 
3469  [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
3470  [columns getIndexes:columnArray maxCount:-1 inIndexRange:nil];
3471 
3472  var columnIndex = 0,
3473  columnsCount = columnArray.length;
3474 
3475  for (; columnIndex < columnsCount; ++columnIndex)
3476  {
3477  var column = columnArray[columnIndex],
3478  tableColumn = _tableColumns[column],
3479  tableColumnUID = [tableColumn UID],
3480  rowIndex = 0,
3481  rowsCount = rowArray.length;
3482 
3483  for (; rowIndex < rowsCount; ++rowIndex)
3484  {
3485  var row = rowArray[rowIndex],
3486  dataViews = _dataViewsForTableColumns[tableColumnUID];
3487 
3488  if (!dataViews || row >= dataViews.length)
3489  continue;
3490 
3491  if (row === _editingRow && column === _editingColumn)
3492  [[self window] makeFirstResponder:self];
3493 
3494  var dataView = [dataViews objectAtIndex:row];
3495 
3496  [dataViews replaceObjectAtIndex:row withObject:nil];
3497 
3498  [self _enqueueReusableDataView:dataView];
3499  }
3500  }
3501 }
3502 
3506 - (void)_loadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
3507 {
3508  if (![rows count] || ![columns count])
3509  return;
3510 
3511  var rowArray = [],
3512  rowRects = [],
3513  columnArray = [];
3514 
3515  [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
3516  [columns getIndexes:columnArray maxCount:-1 inIndexRange:nil];
3517 
3519 
3520  var columnIndex = 0,
3521  columnsCount = columnArray.length;
3522 
3523  for (; columnIndex < columnsCount; ++columnIndex)
3524  {
3525  var column = columnArray[columnIndex],
3526  tableColumn = _tableColumns[column];
3527 
3528  if ([tableColumn isHidden] || tableColumn === _draggedColumn)
3529  continue;
3530 
3531  var tableColumnUID = [tableColumn UID];
3532 
3533  if (!_dataViewsForTableColumns[tableColumnUID])
3534  _dataViewsForTableColumns[tableColumnUID] = [];
3535 
3536  var rowIndex = 0,
3537  rowsCount = rowArray.length,
3538  isColumnSelected = [_selectedColumnIndexes containsIndex:column];
3539 
3540  for (; rowIndex < rowsCount; ++rowIndex)
3541  {
3542  var row = rowArray[rowIndex],
3543  dataView = [self _newDataViewForRow:row tableColumn:tableColumn],
3544  isButton = [dataView isKindOfClass:[CPButton class]],
3545  isTextField = [dataView isKindOfClass:[CPTextField class]];
3546 
3547  [dataView setFrame:[self frameOfDataViewAtColumn:column row:row]];
3548 
3549  [self _setObjectValueForTableColumn:tableColumn row:row forView:dataView];
3550 
3551  if ((_selectionHighlightStyle !== CPTableViewSelectionHighlightStyleNone) &&
3552  (isColumnSelected || [self isRowSelected:row]))
3553  {
3554  [dataView setThemeState:CPThemeStateSelectedDataView];
3555  }
3556  else
3557  [dataView unsetThemeState:CPThemeStateSelectedDataView];
3558 
3559  // FIX ME: for performance reasons we might consider diverging from cocoa and moving this to the reloadData method
3560  if ([self _sendDelegateIsGroupRow:row])
3561  {
3562  [_groupRows addIndex:row];
3563  [dataView setThemeState:CPThemeStateGroupRow];
3564 
3565  }
3566  else
3567  {
3568  [_groupRows removeIndexesInRange:CPMakeRange(row, 1)];
3569  [dataView unsetThemeState:CPThemeStateGroupRow];
3570  }
3571 
3572  [self _sendDelegateWillDisplayView:dataView forTableColumn:tableColumn row:row];
3573  [self setNeedsDisplay:YES];
3574 
3575  if ([dataView superview] !== self)
3576  [self addSubview:dataView];
3577 
3578  _dataViewsForTableColumns[tableColumnUID][row] = dataView;
3579 
3580  if (_isViewBased)
3581  continue;
3582 
3583  if (isButton || (_editingCellIndex && _editingCellIndex.x === column && _editingCellIndex.y === row))
3584  {
3585  if (isTextField)
3586  {
3587  [dataView setEditable:YES];
3588  [dataView setSendsActionOnEndEditing:YES];
3589  [dataView setSelectable:YES];
3590  [dataView selectText:nil];
3591  [dataView setBezeled:YES];
3592  [dataView setDelegate:self];
3593  }
3594 
3595  [dataView setTarget:self];
3596  [dataView setAction:@selector(_commitDataViewObjectValue:)];
3597  dataView.tableViewEditedColumnObj = tableColumn;
3598  dataView.tableViewEditedRowIndex = row;
3599  }
3600  else if (isTextField)
3601  {
3602  [dataView setEditable:NO];
3603  [dataView setSelectable:NO];
3604  }
3605  }
3606  }
3607 }
3608 
3609 - (void)_setObjectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow forView:(CPView)aDataView
3610 {
3611  if ([self _dataSourceRespondsToObjectValueForTableColumn])
3612  [aDataView setObjectValue:[self _objectValueForTableColumn:aTableColumn row:aRow]];
3613 
3614  // This gives the table column an opportunity to apply its bindings.
3615  // It will override the value set above if there is a binding.
3616 
3617  if (_contentBindingExplicitlySet)
3618  [self _prepareContentBindedDataView:aDataView forRow:aRow];
3619  else
3620  // For both cell-based and view-based
3621  [aTableColumn _prepareDataView:aDataView forRow:aRow];
3622 }
3623 
3624 - (void)_prepareContentBindedDataView:(CPView)dataView forRow:(CPInteger)aRow
3625 {
3626  var binder = [CPTableContentBinder getBinding:@"content" forObject:self],
3627  content = [binder content],
3628  rowContent = [content objectAtIndex:aRow];
3629 
3630  [dataView setObjectValue:rowContent];
3631 }
3632 
3636 - (void)_layoutDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
3637 {
3638  var rowArray = [],
3639  columnArray = [];
3640 
3641  [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
3642  [columns getIndexes:columnArray maxCount:-1 inIndexRange:nil];
3643 
3644  var columnIndex = 0,
3645  columnsCount = columnArray.length;
3646 
3647  for (; columnIndex < columnsCount; ++columnIndex)
3648  {
3649  var column = columnArray[columnIndex],
3650  tableColumn = _tableColumns[column],
3651  tableColumnUID = [tableColumn UID],
3652  dataViewsForTableColumn = _dataViewsForTableColumns[tableColumnUID],
3653  rowIndex = 0,
3654  rowsCount = rowArray.length;
3655 
3656  if (dataViewsForTableColumn)
3657  {
3658  for (; rowIndex < rowsCount; ++rowIndex)
3659  {
3660  var row = rowArray[rowIndex],
3661  dataView = dataViewsForTableColumn[row];
3662 
3663  [dataView setFrame:[self frameOfDataViewAtColumn:column row:row]];
3664  }
3665  }
3666  }
3667 }
3668 
3674 - (void)_commitDataViewObjectValue:(id)sender
3675 {
3676  /*
3677  makeFirstResponder at the end of this method causes the dataview to resign.
3678  If the dataview resigning triggers the action (as CPTextField does), we come right
3679  back here and start an infinite loop. So we have to check this flag first.
3680  */
3681  if ([sender respondsToSelector:@selector(sendsActionOnEndEditing)] && [sender sendsActionOnEndEditing] && _editingCellIndex === nil)
3682  return;
3683 
3684  _editingCellIndex = nil;
3685 
3686  [self _sendDataSourceSetObjectValue:[sender objectValue] forTableColumn:sender.tableViewEditedColumnObj row:sender.tableViewEditedRowIndex];
3687 
3688  // Allow the column binding to do a reverse set. Note that we do this even if the data source method above
3689  // is implemented.
3690  [sender.tableViewEditedColumnObj _reverseSetDataView:sender forRow:sender.tableViewEditedRowIndex];
3691 
3692  if ([sender respondsToSelector:@selector(setEditable:)])
3693  [sender setEditable:NO];
3694 
3695  if ([sender respondsToSelector:@selector(setSelectable:)])
3696  [sender setSelectable:NO];
3697 
3698  if ([sender isKindOfClass:[CPTextField class]])
3699  [sender setBezeled:NO];
3700 
3701  [self reloadDataForRowIndexes:[CPIndexSet indexSetWithIndex:sender.tableViewEditedRowIndex]
3702  columnIndexes:[CPIndexSet indexSetWithIndex:[_tableColumns indexOfObject:sender.tableViewEditedColumnObj]]];
3703 
3704  [[self window] makeFirstResponder:self];
3705 
3706 }
3707 
3713 - (void)controlTextDidBlur:(CPNotification)theNotification
3714 {
3715  var dataView = [theNotification object];
3716 
3717  if ([dataView respondsToSelector:@selector(setEditable:)])
3718  [dataView setEditable:NO];
3719 
3720  if ([dataView respondsToSelector:@selector(setSelectable:)])
3721  [dataView setSelectable:NO];
3722 
3723  if ([dataView isKindOfClass:[CPTextField class]])
3724  [dataView setBezeled:NO];
3725 
3726  _editingCellIndex = nil;
3727 }
3728 
3732 - (CPView)_newDataViewForRow:(CPInteger)aRow tableColumn:(CPTableColumn)aTableColumn
3733 {
3734  var view = nil;
3735 
3736  if (_viewForTableColumnRowSelector)
3737  view = objj_msgSend(self, _viewForTableColumnRowSelector, aTableColumn, aRow);
3738 
3739  if (!view)
3740  {
3741  var columnIdentifier = [aTableColumn identifier];
3742 
3743  // For Pre-Lion nibs, there is no automatic identifier for table column; use UID as identifier.
3744  if (!columnIdentifier)
3745  columnIdentifier = [aTableColumn UID];
3746 
3747  view = [self makeViewWithIdentifier:columnIdentifier owner:_delegate];
3748 
3749  if (!view)
3750  view = [aTableColumn _newDataView];
3751 
3752  [view setIdentifier:columnIdentifier];
3753  }
3754 
3755  return view;
3756 }
3757 
3758 /*
3759  Returns a view with the specified identifier.
3760 
3761  @param identifier The view identifier. Must not be nil.
3762  @param owner The owner of the CIB that may be loaded and instantiated to create a new view with the particular identifier.
3763  @return A view for the row.
3764 
3765  @discussion
3766  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.
3767 
3768  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.
3769 */
3770 - (id)makeViewWithIdentifier:(CPString)anIdentifier owner:(id)anOwner
3771 {
3772  if (!anIdentifier)
3773  return nil;
3774 
3775  var view,
3776  // See if we have some reusable views available
3777  reusableViews = _cachedDataViews[anIdentifier];
3778 
3779  if (reusableViews && reusableViews.length)
3780  view = reusableViews.pop();
3781  // Otherwise see if we have a view in the cib with this identifier
3782  else if (_isViewBased)
3783  view = [self _unarchiveViewWithIdentifier:anIdentifier owner:anOwner];
3784 
3785  return view;
3786 }
3787 
3791 - (CPView)_unarchiveViewWithIdentifier:(CPString)anIdentifier owner:(id)anOwner
3792 {
3793  var cib = [_archivedDataViews objectForKey:anIdentifier];
3794 
3795  if (!cib && !_unavailable_custom_cibs[anIdentifier])
3796  {
3797  var bundle = anOwner ? [CPBundle bundleForClass:[anOwner class]] : [CPBundle mainBundle];
3798  cib = [[CPCib alloc] initWithCibNamed:anIdentifier bundle:bundle];
3799  }
3800 
3801  if (!cib)
3802  {
3803  _unavailable_custom_cibs[anIdentifier] = YES;
3804  return nil;
3805  }
3806 
3807  var objects = [],
3808  load = [cib instantiateCibWithOwner:anOwner topLevelObjects:objects];
3809 
3810  if (!load)
3811  return nil;
3812 
3813  var count = objects.length;
3814 
3815  while (count--)
3816  {
3817  var obj = objects[count];
3818 
3819  if ([obj isKindOfClass:[CPView class]])
3820  {
3821  [obj setIdentifier:anIdentifier];
3822  [obj setAutoresizingMask:CPViewNotSizable];
3823 
3824  return obj;
3825  }
3826  }
3827 
3828  return nil;
3829 }
3830 
3831 - (void)_updateIsViewBased
3832 {
3833  if ([self _delegateRespondsToViewForTableColumn])
3834  _viewForTableColumnRowSelector = @selector(_sendDelegateViewForTableColumn:row:);
3835  else if ([self _delegateRespondsToDataViewForTableColumn])
3836  _viewForTableColumnRowSelector = @selector(_sendDelegateDataViewForTableColumn:row:);
3837 
3838  _isViewBased = (_viewForTableColumnRowSelector !== nil || _archivedDataViews !== nil);
3839 }
3840 
3844 - (void)_enqueueReusableDataView:(CPView)aDataView
3845 {
3846  if (!aDataView)
3847  return;
3848 
3849  // FIXME: yuck!
3850  var identifier = [aDataView identifier];
3851 
3852  if (!_cachedDataViews[identifier])
3853  _cachedDataViews[identifier] = [aDataView];
3854  else
3855  _cachedDataViews[identifier].push(aDataView);
3856 }
3857 
3862 - (void)setFrameSize:(CGSize)aSize
3863 {
3864  [super setFrameSize:aSize];
3865 
3866  if (_headerView)
3867  [_headerView setFrameSize:CGSizeMake(CGRectGetWidth([self frame]), CGRectGetHeight([_headerView frame]))];
3868 
3869  _exposedRect = nil;
3870 }
3871 
3875 - (void)setFrameOrigin:(CGPoint)aPoint
3876 {
3877  [super setFrameOrigin:aPoint];
3878 
3879  _exposedRect = nil;
3880 }
3881 
3885 - (void)setBoundsOrigin:(CGPoint)aPoint
3886 {
3887  [super setBoundsOrigin:aPoint];
3888 
3889  _exposedRect = nil;
3890 }
3891 
3895 - (void)setBoundsSize:(CGSize)aSize
3896 {
3897  [super setBoundsSize:aSize];
3898 
3899  _exposedRect = nil;
3900 }
3901 
3905 - (void)setNeedsDisplay:(BOOL)aFlag
3906 {
3907  [super setNeedsDisplay:aFlag];
3908  [_tableDrawView setNeedsDisplay:aFlag];
3909 
3910  [[self headerView] setNeedsDisplay:YES];
3911 }
3912 
3916 - (void)setNeedsLayout
3917 {
3918  [super setNeedsLayout];
3919  [[self headerView] setNeedsLayout];
3920 }
3921 
3925 - (BOOL)_isFocused
3926 {
3927  var isEditing = _editingRow !== CPNotFound || _editingCellIndex;
3928 
3929  return [[self window] isKeyWindow] && ([[self window] firstResponder] === self || isEditing);
3930 }
3931 
3935 - (void)_drawRect:(CGRect)aRect
3936 {
3937  // FIX ME: All three of these methods will likely need to be rewritten for 1.0
3938  // We've got grid drawing in highlightSelection and crap everywhere.
3939 
3940  var exposedRect = [self exposedRect];
3941 
3942  [self drawBackgroundInClipRect:exposedRect];
3943  [self highlightSelectionInClipRect:exposedRect];
3944  [self drawGridInClipRect:exposedRect];
3945 
3946  if (_implementsCustomDrawRow)
3947  [self _drawRows:_exposedRows clipRect:exposedRect];
3948 }
3949 
3955 - (void)drawBackgroundInClipRect:(CGRect)aRect
3956 {
3957  if (!_usesAlternatingRowBackgroundColors)
3958  return;
3959 
3960  var rowColors = [self alternatingRowBackgroundColors],
3961  colorCount = [rowColors count];
3962 
3963  if (colorCount === 0)
3964  return;
3965 
3966  var context = [[CPGraphicsContext currentContext] graphicsPort];
3967 
3968  if (colorCount === 1)
3969  {
3970  CGContextSetFillColor(context, rowColors[0]);
3971  CGContextFillRect(context, aRect);
3972 
3973  return;
3974  }
3975 
3976  var exposedRows = [self _exposedRowsInRect:aRect],
3977  firstRow = FLOOR(exposedRows.location / colorCount) * colorCount,
3978  lastRow = CPMaxRange(exposedRows) - 1,
3979  colorIndex = 0,
3980  groupRowRects = [];
3981 
3982  //loop through each color so we only draw once for each color
3983  while (colorIndex < colorCount)
3984  {
3985  CGContextBeginPath(context);
3986 
3987  for (var row = firstRow + colorIndex; row <= lastRow; row += colorCount)
3988  {
3989  // if it's not a group row draw it otherwise we draw it later
3990  if (![_groupRows containsIndex:row])
3991  CGContextAddRect(context, CGRectIntersection(aRect, [self _rectOfRow:row checkRange:NO]));
3992  else
3993  groupRowRects.push(CGRectIntersection(aRect, [self _rectOfRow:row checkRange:NO]));
3994  }
3995 
3996  CGContextClosePath(context);
3997 
3998  CGContextSetFillColor(context, rowColors[colorIndex]);
3999  CGContextFillPath(context);
4000 
4001  colorIndex++;
4002  }
4003 
4004  [self _drawGroupRowsForRects:groupRowRects];
4005 }
4006 
4011 - (void)drawGridInClipRect:(CGRect)aRect
4012 {
4013  var context = [[CPGraphicsContext currentContext] graphicsPort],
4014  gridStyleMask = [self gridStyleMask],
4015  lineThickness = [self currentValueForThemeAttribute:@"grid-line-thickness"];
4016 
4017  if (!(gridStyleMask & (CPTableViewSolidHorizontalGridLineMask | CPTableViewSolidVerticalGridLineMask)))
4018  return;
4019 
4020  CGContextBeginPath(context);
4021 
4022  if (gridStyleMask & CPTableViewSolidHorizontalGridLineMask)
4023  {
4024  var exposedRows = [self _exposedRowsInRect:aRect],
4025  row = exposedRows.location,
4026  lastRow = CPMaxRange(exposedRows) - 1,
4027  rowY = -lineThickness / 2,
4028  minX = CGRectGetMinX(aRect),
4029  maxX = CGRectGetMaxX(aRect);
4030 
4031  for (; row <= lastRow; ++row)
4032  {
4033  // grab each row rect and add the top and bottom lines
4034  var rowRect = [self _rectOfRow:row checkRange:NO],
4035  rowY = CGRectGetMaxY(rowRect) - lineThickness / 2;
4036 
4037  CGContextMoveToPoint(context, minX, rowY);
4038  CGContextAddLineToPoint(context, maxX, rowY);
4039  }
4040 
4041  if (_rowHeight > 0.0)
4042  {
4043  var rowHeight = FULL_ROW_HEIGHT(),
4044  totalHeight = CGRectGetMaxY(aRect) - lineThickness / 2;
4045 
4046  while (rowY < totalHeight)
4047  {
4048  rowY += rowHeight;
4049 
4050  CGContextMoveToPoint(context, minX, rowY);
4051  CGContextAddLineToPoint(context, maxX, rowY);
4052  }
4053  }
4054  }
4055 
4056  if (gridStyleMask & CPTableViewSolidVerticalGridLineMask)
4057  {
4058  var exposedColumnIndexes = [self columnIndexesInRect:aRect],
4059  columnsArray = [];
4060 
4061  [exposedColumnIndexes getIndexes:columnsArray maxCount:-1 inIndexRange:nil];
4062 
4063  var columnArrayIndex = 0,
4064  columnArrayCount = columnsArray.length,
4065  minY = CGRectGetMinY(aRect),
4066  maxY = CGRectGetMaxY(aRect);
4067 
4068  for (; columnArrayIndex < columnArrayCount; ++columnArrayIndex)
4069  {
4070  var columnRect = [self rectOfColumn:columnsArray[columnArrayIndex]],
4071  columnX = CGRectGetMaxX(columnRect) - lineThickness / 2;
4072 
4073  CGContextMoveToPoint(context, columnX, minY);
4074  CGContextAddLineToPoint(context, columnX, maxY);
4075  }
4076  }
4077 
4078  CGContextClosePath(context);
4079  CGContextSetStrokeColor(context, [self gridColor]);
4080  CGContextSetLineWidth(context, lineThickness);
4081  CGContextStrokePath(context);
4082 }
4083 
4089 - (void)highlightSelectionInClipRect:(CGRect)aRect
4090 {
4091  if (_selectionHighlightStyle === CPTableViewSelectionHighlightStyleNone)
4092  return;
4093 
4094  var context = [[CPGraphicsContext currentContext] graphicsPort],
4095  indexes = [],
4096  rectSelector = @selector(rectOfRow:);
4097 
4098  if ([_selectedRowIndexes count] >= 1)
4099  {
4100  var exposedRows = [CPIndexSet indexSetWithIndexesInRange:[self rowsInRect:aRect]],
4101  firstRow = [exposedRows firstIndex],
4102  exposedRange = CPMakeRange(firstRow, [exposedRows lastIndex] - firstRow + 1);
4103 
4104  [_selectedRowIndexes getIndexes:indexes maxCount:-1 inIndexRange:exposedRange];
4105  }
4106  else if ([_selectedColumnIndexes count] >= 1)
4107  {
4108  rectSelector = @selector(rectOfColumn:);
4109 
4110  var exposedColumns = [self columnIndexesInRect:aRect],
4111  firstColumn = [exposedColumns firstIndex],
4112  exposedRange = CPMakeRange(firstColumn, [exposedColumns lastIndex] - firstColumn + 1);
4113 
4114  [_selectedColumnIndexes getIndexes:indexes maxCount:-1 inIndexRange:exposedRange];
4115  }
4116 
4117  var count,
4118  count2 = count = [indexes count];
4119 
4120  if (!count)
4121  return;
4122 
4123  var drawGradient = (CPFeatureIsCompatible(CPHTMLCanvasFeature) && _selectionHighlightStyle === CPTableViewSelectionHighlightStyleSourceList && [_selectedRowIndexes count] >= 1),
4124  deltaHeight = 0.5 * (_gridStyleMask & CPTableViewSolidHorizontalGridLineMask),
4125  focused = [self _isFocused];
4126 
4127  CGContextBeginPath(context);
4128 
4129  if (drawGradient)
4130  {
4131  var gradientCache = focused ? [self selectionGradientColors] : [self unfocusedSelectionGradientColors],
4132  topLineColor = [gradientCache objectForKey:CPSourceListTopLineColor],
4133  bottomLineColor = [gradientCache objectForKey:CPSourceListBottomLineColor],
4134  gradientColor = [gradientCache objectForKey:CPSourceListGradient];
4135  }
4136 
4137  var normalSelectionHighlightColor = focused ? [self selectionHighlightColor] : [self unfocusedSelectionHighlightColor];
4138 
4139  // don't do these lookups if there are no group rows
4140  if ([_groupRows count])
4141  {
4142  var topGroupLineColor = [CPColor colorWithCalibratedWhite:212.0 / 255.0 alpha:1.0],
4143  bottomGroupLineColor = [CPColor colorWithCalibratedWhite:185.0 / 255.0 alpha:1.0],
4144  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);
4145  }
4146 
4147  while (count--)
4148  {
4149  var currentIndex = indexes[count],
4150  rowRect = CGRectIntersection(objj_msgSend(self, rectSelector, currentIndex), aRect);
4151 
4152  // group rows get the same highlight style as other rows if they're source list...
4153  if (!drawGradient)
4154  var shouldUseGroupGradient = [_groupRows containsIndex:currentIndex];
4155 
4156  if (drawGradient || shouldUseGroupGradient)
4157  {
4158  var minX = CGRectGetMinX(rowRect),
4159  minY = CGRectGetMinY(rowRect),
4160  maxX = CGRectGetMaxX(rowRect),
4161  maxY = CGRectGetMaxY(rowRect) - deltaHeight;
4162 
4163  if (!drawGradient)
4164  {
4165  //If there is no source list gradient we need to close the selection path and fill it now
4166  [normalSelectionHighlightColor setFill];
4167  CGContextClosePath(context);
4168  CGContextFillPath(context);
4169  CGContextBeginPath(context);
4170  }
4171 
4172  CGContextAddRect(context, rowRect);
4173 
4174  CGContextDrawLinearGradient(context, (shouldUseGroupGradient) ? gradientGroupColor : gradientColor, rowRect.origin, CGPointMake(minX, maxY), 0);
4175 
4176  CGContextBeginPath(context);
4177  CGContextMoveToPoint(context, minX, minY + .5);
4178  CGContextAddLineToPoint(context, maxX, minY + .5);
4179  CGContextSetStrokeColor(context, (shouldUseGroupGradient) ? topGroupLineColor : topLineColor);
4180  CGContextStrokePath(context);
4181 
4182  CGContextBeginPath(context);
4183  CGContextMoveToPoint(context, minX, maxY - .5);
4184  CGContextAddLineToPoint(context, maxX, maxY - .5);
4185  CGContextSetStrokeColor(context, (shouldUseGroupGradient) ? bottomGroupLineColor : bottomLineColor);
4186  CGContextStrokePath(context);
4187  }
4188  else
4189  {
4190  var radius = [self currentValueForThemeAttribute:@"selection-radius"];
4191 
4192  if (radius > 0)
4193  {
4194  var minX = CGRectGetMinX(rowRect),
4195  maxX = CGRectGetMaxX(rowRect),
4196  minY = CGRectGetMinY(rowRect),
4197  maxY = CGRectGetMaxY(rowRect);
4198 
4199  CGContextMoveToPoint(context, minX + radius, minY);
4200  CGContextAddArcToPoint(context, maxX, minY, maxX, minY + radius, radius);
4201  CGContextAddArcToPoint(context, maxX, maxY, maxX - radius, maxY, radius);
4202  CGContextAddArcToPoint(context, minX, maxY, minX, maxY - radius, radius);
4203  CGContextAddArcToPoint(context, minX, minY, minX + radius, minY, radius);
4204  }
4205  else
4206  CGContextAddRect(context, rowRect);
4207  }
4208  }
4209 
4210  CGContextClosePath(context);
4211 
4212  if (!drawGradient)
4213  {
4214  [normalSelectionHighlightColor setFill];
4215  CGContextFillPath(context);
4216  }
4217 
4218  CGContextBeginPath(context);
4219 
4220  var gridStyleMask = [self gridStyleMask];
4221 
4222  for (var i = 0; i < count2; i++)
4223  {
4224  var rect = objj_msgSend(self, rectSelector, indexes[i]),
4225  minX = CGRectGetMinX(rect) - 0.5,
4226  maxX = CGRectGetMaxX(rect) - 0.5,
4227  minY = CGRectGetMinY(rect) - 0.5,
4228  maxY = CGRectGetMaxY(rect) - 0.5;
4229 
4230  if ([_selectedRowIndexes count] >= 1 && gridStyleMask & CPTableViewSolidVerticalGridLineMask)
4231  {
4232  var exposedColumns = [self columnIndexesInRect:aRect],
4233  exposedColumnIndexes = [],
4234  firstExposedColumn = [exposedColumns firstIndex],
4235  exposedRange = CPMakeRange(firstExposedColumn, [exposedColumns lastIndex] - firstExposedColumn + 1);
4236 
4237  [exposedColumns getIndexes:exposedColumnIndexes maxCount:-1 inIndexRange:exposedRange];
4238 
4239  var exposedColumnCount = [exposedColumnIndexes count];
4240 
4241  for (var c = firstExposedColumn; c < exposedColumnCount; c++)
4242  {
4243  var colRect = [self rectOfColumn:exposedColumnIndexes[c]],
4244  colX = CGRectGetMaxX(colRect) + 0.5;
4245 
4246  CGContextMoveToPoint(context, colX, minY);
4247  CGContextAddLineToPoint(context, colX, maxY);
4248  }
4249  }
4250 
4251  //if the row after the current row is not selected then there is no need to draw the bottom grid line white.
4252  if ([indexes containsObject:indexes[i] + 1])
4253  {
4254  CGContextMoveToPoint(context, minX, maxY);
4255  CGContextAddLineToPoint(context, maxX, maxY);
4256  }
4257  }
4258 
4259  CGContextClosePath(context);
4260  CGContextSetStrokeColor(context, [self currentValueForThemeAttribute:@"highlighted-grid-color"]);
4261  CGContextStrokePath(context);
4262 }
4263 
4269 - (void)_drawGroupRowsForRects:(CPArray)rects
4270 {
4271  if ((CPFeatureIsCompatible(CPHTMLCanvasFeature) && _selectionHighlightStyle === CPTableViewSelectionHighlightStyleSourceList) || !rects.length)
4272  return;
4273 
4274  var context = [[CPGraphicsContext currentContext] graphicsPort],
4275  i = rects.length;
4276 
4277  CGContextBeginPath(context);
4278 
4279  var gradientCache = [self selectionGradientColors],
4280  topLineColor = [CPColor colorWithHexString:"d3d3d3"],
4281  bottomLineColor = [CPColor colorWithHexString:"bebebd"],
4282  gradientColor = CGGradientCreateWithColorComponents(CGColorSpaceCreateDeviceRGB(), [220.0 / 255.0, 220.0 / 255.0, 220.0 / 255.0, 1.0,
4283  199.0 / 255.0, 199.0 / 255.0, 199.0 / 255.0, 1.0], [0, 1], 2),
4284  drawGradient = YES;
4285 
4286  while (i--)
4287  {
4288  var rowRect = rects[i];
4289 
4290  CGContextAddRect(context, rowRect);
4291 
4292  if (drawGradient)
4293  {
4294  var minX = CGRectGetMinX(rowRect),
4295  minY = CGRectGetMinY(rowRect),
4296  maxX = CGRectGetMaxX(rowRect),
4297  maxY = CGRectGetMaxY(rowRect);
4298 
4299  CGContextDrawLinearGradient(context, gradientColor, rowRect.origin, CGPointMake(minX, maxY), 0);
4300 
4301  CGContextBeginPath(context);
4302  CGContextMoveToPoint(context, minX, minY);
4303  CGContextAddLineToPoint(context, maxX, minY);
4304  CGContextSetStrokeColor(context, topLineColor);
4305  CGContextStrokePath(context);
4306 
4307  CGContextBeginPath(context);
4308  CGContextMoveToPoint(context, minX, maxY);
4309  CGContextAddLineToPoint(context, maxX, maxY - 1);
4310  CGContextSetStrokeColor(context, bottomLineColor);
4311  CGContextStrokePath(context);
4312  }
4313  }
4314 }
4315 
4319 - (void)_drawRows:(CPIndexSet)rowsIndexes clipRect:(CGRect)clipRect
4320 {
4321  var row = [rowsIndexes firstIndex];
4322 
4323  while (row !== CPNotFound)
4324  {
4325  [self drawRow:row clipRect:CGRectIntersection(clipRect, [self rectOfRow:row])];
4326  row = [rowsIndexes indexGreaterThanIndex:row];
4327  }
4328 }
4329 
4336 - (void)drawRow:(CPInteger)row clipRect:(CGRect)rect
4337 {
4338  // This method does currently nothing in cappuccino. Can be overridden by subclasses.
4339 
4340 }
4341 
4345 - (void)layoutSubviews
4346 {
4347  [self load];
4348 }
4349 
4353 - (void)viewWillMoveToSuperview:(CPView)aView
4354 {
4355  var superview = [self superview],
4356  defaultCenter = [CPNotificationCenter defaultCenter];
4357 
4358  if (superview)
4359  {
4360  [defaultCenter
4361  removeObserver:self
4362  name:CPViewFrameDidChangeNotification
4363  object:superview];
4364 
4365  [defaultCenter
4366  removeObserver:self
4367  name:CPViewBoundsDidChangeNotification
4368  object:superview];
4369  }
4370 
4371  if ([aView isKindOfClass:[CPClipView class]])
4372  {
4373  [aView setPostsFrameChangedNotifications:YES];
4374  [aView setPostsBoundsChangedNotifications:YES];
4375 
4376  [defaultCenter
4377  addObserver:self
4378  selector:@selector(superviewFrameChanged:)
4379  name:CPViewFrameDidChangeNotification
4380  object:aView];
4381 
4382  [defaultCenter
4383  addObserver:self
4384  selector:@selector(superviewBoundsChanged:)
4385  name:CPViewBoundsDidChangeNotification
4386  object:aView];
4387  }
4388 }
4389 
4393 - (void)superviewBoundsChanged:(CPNotification)aNotification
4394 {
4395  _exposedRect = nil;
4396 
4397  [self setNeedsDisplay:YES];
4398  [self setNeedsLayout];
4399 }
4400 
4404 - (void)superviewFrameChanged:(CPNotification)aNotification
4405 {
4406  _exposedRect = nil;
4407 
4408  [self tile];
4409 }
4410 
4411 /*
4412  @ignore
4413 */
4414 - (BOOL)tracksMouseOutsideOfFrame
4415 {
4416  return YES;
4417 }
4418 
4419 /*
4420  @ignore
4421 */
4422 - (BOOL)startTrackingAt:(CGPoint)aPoint
4423 {
4424  // Try to become the first responder, but if we can't, that's okay.
4425  [[self window] makeFirstResponder:self];
4426 
4427  var row = [self rowAtPoint:aPoint];
4428 
4429  // If the user clicks outside a row then deselect everything.
4430  if (row < 0 && _allowsEmptySelection)
4431  {
4432  if ([self _sendDelegateSelectionShouldChangeInTableView])
4433  {
4434  var indexSet = [self _sendDelegateSelectionIndexesForProposedSelection:[CPIndexSet indexSet]];
4435 
4436  [self _noteSelectionIsChanging];
4437  [self selectRowIndexes:indexSet byExtendingSelection:NO];
4438  }
4439  }
4440 
4441  if ([self mouseDownFlags] & CPShiftKeyMask)
4442  _selectionAnchorRow = (ABS([_selectedRowIndexes firstIndex] - row) < ABS([_selectedRowIndexes lastIndex] - row)) ?
4443  [_selectedRowIndexes firstIndex] : [_selectedRowIndexes lastIndex];
4444  else
4445  _selectionAnchorRow = row;
4446 
4447  //set ivars for startTrackingPoint and time...
4448  _startTrackingPoint = aPoint;
4449  _startTrackingTimestamp = new Date();
4450 
4451  if ([self _dataSourceRespondsToSetObjectValueForTableColumnRow])
4452  _trackingPointMovedOutOfClickSlop = NO;
4453 
4454  // if the table has drag support then we use mouseUp to select a single row.
4455  // otherwise it uses mouse down.
4456  if (row >= 0 && !([self _dataSourceRespondsToWriteRowsWithIndexesToPasteboard]))
4457  [self _updateSelectionWithMouseAtRow:row];
4458 
4459  return YES;
4460 }
4461 
4465 - (CPMenu)menuForEvent:(CPEvent)theEvent
4466 {
4467  if (!([self _delegateRespondsToMenuForTableColumnRow]))
4468  return [super menuForEvent:theEvent];
4469 
4470  var location = [self convertPoint:[theEvent locationInWindow] fromView:nil],
4471  row = [self rowAtPoint:location],
4472  column = [self columnAtPoint:location],
4473  tableColumn = [[self tableColumns] objectAtIndex:column];
4474 
4475  return [self _sendDelegateMenuForTableColumn:tableColumn row:row];
4476 }
4477 
4478 /*
4479  @ignore
4480 */
4481 - (void)trackMouse:(CPEvent)anEvent
4482 {
4483  // Prevent CPControl from eating the mouse events when we are in a drag session
4484  if (![_draggedRowIndexes count])
4485  {
4486  [self autoscroll:anEvent];
4487  [super trackMouse:anEvent];
4488  }
4489  else
4490  [CPApp sendEvent:anEvent];
4491 }
4492 
4493 /*
4494  @ignore
4495 */
4496 - (BOOL)continueTracking:(CGPoint)lastPoint at:(CGPoint)aPoint
4497 {
4498  var row = [self rowAtPoint:aPoint];
4499 
4500  // begin the drag is the datasource lets us, we've move at least +-3px vertical or horizontal,
4501  // or we're dragging from selected rows and we haven't begun a drag session
4502  if (!_isSelectingSession && [self _dataSourceRespondsToWriteRowsWithIndexesToPasteboard])
4503  {
4504  if (row >= 0 && (ABS(_startTrackingPoint.x - aPoint.x) > 3 || (_verticalMotionCanDrag && ABS(_startTrackingPoint.y - aPoint.y) > 3)) ||
4505  ([_selectedRowIndexes containsIndex:row]))
4506  {
4507  if ([_selectedRowIndexes containsIndex:row])
4508  _draggedRowIndexes = [[CPIndexSet alloc] initWithIndexSet:_selectedRowIndexes];
4509  else
4510  _draggedRowIndexes = [CPIndexSet indexSetWithIndex:row];
4511 
4512  //ask the datasource for the data
4513  var pboard = [CPPasteboard pasteboardWithName:CPDragPboard];
4514 
4515  if ([self canDragRowsWithIndexes:_draggedRowIndexes atPoint:aPoint] && [self _sendDataSourceWriteRowsWithIndexes:_draggedRowIndexes toPasteboard:pboard])
4516  {
4517  var currentEvent = [CPApp currentEvent],
4518  offset = CGPointMakeZero(),
4519  tableColumns = [_tableColumns objectsAtIndexes:_exposedColumns];
4520 
4521  // We deviate from the default Cocoa implementation here by asking for a view in stead of an image
4522  // We support both, but the view preferred over the image because we can mimic the rows we are dragging
4523  // by re-creating the data views for the dragged rows
4524  var view = [self dragViewForRowsWithIndexes:_draggedRowIndexes
4525  tableColumns:tableColumns
4526  event:currentEvent
4527  offset:offset];
4528 
4529  if (!view)
4530  {
4531  var image = [self dragImageForRowsWithIndexes:_draggedRowIndexes
4532  tableColumns:tableColumns
4533  event:currentEvent
4534  offset:offset];
4535  view = [[CPImageView alloc] initWithFrame:CGRectMake(0, 0, [image size].width, [image size].height)];
4536  [view setImage:image];
4537  }
4538 
4539  var bounds = [view bounds],
4540  viewLocation = CGPointMake(aPoint.x - CGRectGetWidth(bounds) / 2 + offset.x, aPoint.y - CGRectGetHeight(bounds) / 2 + offset.y);
4541  [self dragView:view at:viewLocation offset:CGPointMakeZero() event:[CPApp currentEvent] pasteboard:pboard source:self slideBack:YES];
4542  _startTrackingPoint = nil;
4543 
4544  return NO;
4545  }
4546 
4547  // The delegate disallowed the drag so clear the dragged row indexes
4548  _draggedRowIndexes = [CPIndexSet indexSet];
4549  }
4550  else if (ABS(_startTrackingPoint.x - aPoint.x) < 5 && ABS(_startTrackingPoint.y - aPoint.y) < 5)
4551  return YES;
4552  }
4553 
4554  _isSelectingSession = YES;
4555  if (row >= 0 && row !== _lastTrackedRowIndex)
4556  {
4557  _lastTrackedRowIndex = row;
4558  [self _updateSelectionWithMouseAtRow:row];
4559  }
4560 
4561  if ([self _dataSourceRespondsToSetObjectValueForTableColumnRow]
4562  && !_trackingPointMovedOutOfClickSlop)
4563  {
4564  var CLICK_SPACE_DELTA = 5.0; // Stolen from AppKit/Platform/DOM/CPPlatformWindow+DOM.j
4565  if (ABS(aPoint.x - _startTrackingPoint.x) > CLICK_SPACE_DELTA
4566  || ABS(aPoint.y - _startTrackingPoint.y) > CLICK_SPACE_DELTA)
4567  {
4568  _trackingPointMovedOutOfClickSlop = YES;
4569  }
4570  }
4571 
4572  return YES;
4573 }
4574 
4578 - (void)stopTracking:(CGPoint)lastPoint at:(CGPoint)aPoint mouseIsUp:(BOOL)mouseIsUp
4579 {
4580  _isSelectingSession = NO;
4581 
4582  var CLICK_TIME_DELTA = 1000,
4583  columnIndex = -1,
4584  column,
4585  rowIndex,
4586  shouldEdit = YES;
4587 
4588  if ([self _dataSourceRespondsToWriteRowsWithIndexesToPasteboard])
4589  {
4590  rowIndex = [self rowAtPoint:aPoint];
4591 
4592  if (rowIndex !== -1)
4593  {
4594  if ([_draggedRowIndexes count] > 0)
4595  {
4596  _draggedRowIndexes = [CPIndexSet indexSet];
4597  return;
4598  }
4599  // if the table has drag support then we use mouseUp to select a single row.
4600  _previouslySelectedRowIndexes = [_selectedRowIndexes copy];
4601  [self _updateSelectionWithMouseAtRow:rowIndex];
4602  }
4603  }
4604 
4605  // Accept either tableView:setObjectValue:forTableColumn:row: delegate method, or a binding.
4606  if (!_isViewBased && mouseIsUp
4607  && !_trackingPointMovedOutOfClickSlop
4608  && ([[CPApp currentEvent] clickCount] > 1)
4609  && ([self _dataSourceRespondsToSetObjectValueForTableColumnRow]
4610  || [self infoForBinding:@"content"]))
4611  {
4612  columnIndex = [self columnAtPoint:lastPoint];
4613 
4614  if (columnIndex !== -1)
4615  {
4616  column = _tableColumns[columnIndex];
4617 
4618  if ([column isEditable])
4619  {
4620  rowIndex = [self rowAtPoint:aPoint];
4621 
4622  if (rowIndex !== -1)
4623  {
4624  if ([self _sendDelegateShouldEditTableColumn:column row:rowIndex])
4625  {
4626  [self editColumn:columnIndex row:rowIndex withEvent:nil select:YES];
4627  return;
4628  }
4629  }
4630  }
4631  }
4632 
4633  } //end of editing conditional
4634 
4635  //double click actions
4636  if ([[CPApp currentEvent] clickCount] === 2 && _doubleAction)
4637  {
4638  _clickedRow = [self rowAtPoint:aPoint];
4639  _clickedColumn = [self columnAtPoint:lastPoint];
4640  [self sendAction:_doubleAction to:_target];
4641  }
4642 }
4643 
4644 /*
4645  @ignore
4646 */
4647 - (CPDragOperation)draggingEntered:(id)sender
4648 {
4649  var location = [self convertPoint:[sender draggingLocation] fromView:nil],
4650  dropOperation = [self _proposedDropOperationAtPoint:location],
4651  row = [self _proposedRowAtPoint:location];
4652 
4653  if (_retargetedDropRow !== nil)
4654  row = _retargetedDropRow;
4655 
4656  var draggedTypes = [self registeredDraggedTypes],
4657  count = [draggedTypes count],
4658  i = 0;
4659 
4660  for (; i < count; i++)
4661  {
4662  if ([[[sender draggingPasteboard] types] containsObject:[draggedTypes objectAtIndex: i]])
4663  return [self _sendDataSourceValidateDrop:sender proposedRow:row proposedDropOperation:dropOperation];
4664  }
4665 
4666  return CPDragOperationNone;
4667 }
4668 
4669 /*
4670  @ignore
4671 */
4672 - (void)draggingExited:(id)sender
4673 {
4674  [_dropOperationFeedbackView removeFromSuperview];
4675 }
4676 
4677 /*
4678  @ignore
4679 */
4680 - (void)draggingEnded:(id)sender
4681 {
4682  [self _draggingEnded];
4683 }
4684 
4688 - (void)_draggingEnded
4689 {
4690  _retargetedDropOperation = nil;
4691  _retargetedDropRow = nil;
4692  _draggedRowIndexes = [CPIndexSet indexSet];
4693  [_dropOperationFeedbackView removeFromSuperview];
4694  [self _enqueueDraggingViews];
4695 }
4696 
4697 /*
4698  @ignore
4699 */
4700 - (BOOL)wantsPeriodicDraggingUpdates
4701 {
4702  return YES;
4703 }
4704 
4705 /*
4706  @ignore
4707 */
4708 - (CPTableViewDropOperation)_proposedDropOperationAtPoint:(CGPoint)theDragPoint
4709 {
4710  if (_retargetedDropOperation !== nil)
4711  return _retargetedDropOperation;
4712 
4713  var row = [self _proposedRowAtPoint:theDragPoint],
4714  rowRect = [self rectOfRow:row];
4715 
4716  // If there is no (the default) or too little inter-cell spacing we create some room for the CPTableViewDropAbove indicator
4717  // This probably doesn't work if the row height is smaller than or around 5.0
4718  if ([self intercellSpacing].height < 5.0)
4719  rowRect = CGRectInset(rowRect, 0.0, 5.0 - [self intercellSpacing].height);
4720 
4721  // If the altered row rect contains the drag point we show the drop on
4722  // We don't show the drop on indicator if we are dragging below the last row
4723  // in that case we always want to show the drop above indicator
4724  if (CGRectContainsPoint(rowRect, theDragPoint) && row < _numberOfRows)
4725  return CPTableViewDropOn;
4726 
4727  return CPTableViewDropAbove;
4728 }
4729 
4730 /*
4731  @ignore
4732 */
4733 - (CPInteger)_proposedRowAtPoint:(CGPoint)dragPoint
4734 {
4735  var row = [self rowAtPoint:dragPoint],
4736  // Determine if the mouse is currently closer to this row or the row below it
4737  lowerRow = row + 1,
4738  rect = [self rectOfRow:row],
4739  bottomPoint = CGRectGetMaxY(rect),
4740  bottomThirty = bottomPoint - ((bottomPoint - CGRectGetMinY(rect)) * 0.3),
4741  numberOfRows = [self numberOfRows];
4742 
4743  if (row < 0)
4744  row = (CGRectGetMaxY(rect) < dragPoint.y) ? numberOfRows : row;
4745  else if (dragPoint.y > MAX(bottomThirty, bottomPoint - 6))
4746  row = lowerRow;
4747 
4748  row = MIN(numberOfRows, row);
4749 
4750  return row;
4751 }
4752 
4756 - (CGRect)_rectForDropHighlightViewOnRow:(CPInteger)theRowIndex
4757 {
4758  if (theRowIndex >= [self numberOfRows])
4759  theRowIndex = [self numberOfRows] - 1;
4760 
4761  return [self _rectOfRow:theRowIndex checkRange:NO];
4762 }
4763 
4767 - (CGRect)_rectForDropHighlightViewBetweenUpperRow:(CPInteger)theUpperRowIndex andLowerRow:(CPInteger)theLowerRowIndex offset:(CGPoint)theOffset
4768 {
4769  if (theLowerRowIndex > [self numberOfRows])
4770  theLowerRowIndex = [self numberOfRows];
4771 
4772  return [self _rectOfRow:theLowerRowIndex checkRange:NO];
4773 }
4774 
4778 - (CPDragOperation)draggingUpdated:(id)sender
4779 {
4780  _retargetedDropRow = nil;
4781  _retargetedDropOperation = nil;
4782 
4783  var location = [self convertPoint:[sender draggingLocation] fromView:nil],
4784  dropOperation = [self _proposedDropOperationAtPoint:location],
4785  numberOfRows = [self numberOfRows],
4786  row = [self _proposedRowAtPoint:location],
4787  dragOperation = [self _sendDataSourceValidateDrop:sender proposedRow:row proposedDropOperation:dropOperation];
4788 
4789  if (_retargetedDropRow !== nil)
4790  row = _retargetedDropRow;
4791  if (_retargetedDropOperation !== nil)
4792  dropOperation = _retargetedDropOperation;
4793 
4794 
4795  if (dropOperation === CPTableViewDropOn && row >= numberOfRows)
4796  row = numberOfRows - 1;
4797 
4798  var rect = CGRectMakeZero();
4799 
4800  if (row === -1)
4801  rect = [self exposedRect];
4802 
4803  else if (dropOperation === CPTableViewDropAbove)
4804  rect = [self _rectForDropHighlightViewBetweenUpperRow:row - 1 andLowerRow:row offset:location];
4805 
4806  else
4807  rect = [self _rectForDropHighlightViewOnRow:row];
4808 
4809  [_dropOperationFeedbackView setDropOperation:row !== -1 ? dropOperation : CPDragOperationNone];
4810  [_dropOperationFeedbackView setHidden:(dragOperation == CPDragOperationNone)];
4811  [_dropOperationFeedbackView setFrame:rect];
4812  [_dropOperationFeedbackView setCurrentRow:row];
4813  [self addSubview:_dropOperationFeedbackView];
4814 
4815  return dragOperation;
4816 }
4817 
4818 /*
4819  @ignore
4820 */
4821 - (BOOL)prepareForDragOperation:(id)sender
4822 {
4823  // FIX ME: is there anything else that needs to happen here?
4824  // actual validation is called in draggingUpdated:
4825  [_dropOperationFeedbackView removeFromSuperview];
4826 
4827  return [self _dataSourceRespondsToValidateDropProposedRowProposedDropOperation];
4828 }
4829 
4830 /*
4831  @ignore
4832 */
4833 - (BOOL)performDragOperation:(id)sender
4834 {
4835  var location = [self convertPoint:[sender draggingLocation] fromView:nil],
4836  operation = [self _proposedDropOperationAtPoint:location],
4837  row = _retargetedDropRow;
4838 
4839  if (row === nil)
4840  var row = [self _proposedRowAtPoint:location];
4841 
4842  return [self _sendDataSourceAcceptDrop:sender row:row dropOperation:operation];
4843 }
4844 
4845 /*
4846  @ignore
4847 */
4848 - (void)concludeDragOperation:(id)sender
4849 {
4850  [self reloadData];
4851 }
4852 
4853 /*
4854  @ignore
4855  We're using this because we drag views instead of images so we can get the rows themselves to actually drag.
4856 */
4857 - (void)draggedView:(CPImage)aView endedAt:(CGPoint)aLocation operation:(CPDragOperation)anOperation
4858 {
4859  [self _draggingEnded];
4860  [self draggedImage:aView endedAt:aLocation operation:anOperation];
4861 }
4862 
4863 - (void)_enqueueDraggingViews
4864 {
4865  [_draggingViews enumerateObjectsUsingBlock:function(dataView, idx)
4866  {
4867  [self _enqueueReusableDataView:dataView];
4868  }];
4869 
4870  [_draggingViews removeAllObjects];
4871 }
4872 
4876 - (void)_updateSelectionWithMouseAtRow:(CPInteger)aRow
4877 {
4878  //check to make sure the row exists
4879  if (aRow < 0)
4880  return;
4881 
4882  var newSelection,
4883  shouldExtendSelection = NO;
4884 
4885  // If cmd/ctrl was held down XOR the old selection with the proposed selection
4886  if ([self mouseDownFlags] & (CPCommandKeyMask | CPControlKeyMask | CPAlternateKeyMask))
4887  {
4888  if ([_selectedRowIndexes containsIndex:aRow])
4889  {
4890  newSelection = [_selectedRowIndexes copy];
4891 
4892  [newSelection removeIndex:aRow];
4893  }
4894  else if (_allowsMultipleSelection)
4895  {
4896  newSelection = [_selectedRowIndexes copy];
4897 
4898  [newSelection addIndex:aRow];
4899  }
4900  else
4901  newSelection = [CPIndexSet indexSetWithIndex:aRow];
4902  }
4903  else if (_allowsMultipleSelection)
4904  {
4905  if (_selectionAnchorRow == CPNotFound)
4906  _selectionAnchorRow = [self numberOfRows] - 1;
4907 
4908  newSelection = [CPIndexSet indexSetWithIndexesInRange:CPMakeRange(MIN(aRow, _selectionAnchorRow), ABS(aRow - _selectionAnchorRow) + 1)];
4909  shouldExtendSelection = [self mouseDownFlags] & CPShiftKeyMask &&
4910  ((_lastSelectedRow == [_selectedRowIndexes lastIndex] && aRow > _lastSelectedRow) ||
4911  (_lastSelectedRow == [_selectedRowIndexes firstIndex] && aRow < _lastSelectedRow));
4912  }
4913  else if (aRow >= 0 && aRow < _numberOfRows)
4914  newSelection = [CPIndexSet indexSetWithIndex:aRow];
4915  else
4916  newSelection = [CPIndexSet indexSet];
4917 
4918  if ([newSelection isEqualToIndexSet:_selectedRowIndexes])
4919  return;
4920 
4921  if (![self _sendDelegateSelectionShouldChangeInTableView])
4922  return;
4923 
4924  newSelection = [self _sendDelegateSelectionIndexesForProposedSelection:newSelection];
4925 
4926  if (![self _delegateRespondsToSelectionIndexesForProposedSelection] && [self _delegateRespondsToShouldSelectRow])
4927  {
4928  var indexArray = [];
4929 
4930  [newSelection getIndexes:indexArray maxCount:-1 inIndexRange:nil];
4931 
4932  var indexCount = indexArray.length;
4933 
4934  while (indexCount--)
4935  {
4936  var index = indexArray[indexCount];
4937 
4938  if (![self _sendDelegateShouldSelectRow:index])
4939  [newSelection removeIndex:index];
4940  }
4941 
4942  // as per cocoa
4943  if ([newSelection count] === 0)
4944  return;
4945  }
4946 
4947  // if empty selection is not allowed and the new selection has nothing selected, abort
4948  if (!_allowsEmptySelection && [newSelection count] === 0)
4949  return;
4950 
4951  if ([newSelection isEqualToIndexSet:_selectedRowIndexes])
4952  return;
4953 
4954  [self _noteSelectionIsChanging];
4955  [self selectRowIndexes:newSelection byExtendingSelection:shouldExtendSelection];
4956 
4957  _lastSelectedRow = [newSelection containsIndex:aRow] ? aRow : [newSelection lastIndex];
4958 }
4959 
4963 - (void)_noteSelectionIsChanging
4964 {
4966  postNotificationName:CPTableViewSelectionIsChangingNotification
4967  object:self
4968  userInfo:nil];
4969 }
4970 
4974 - (void)_noteSelectionDidChange
4975 {
4977  postNotificationName:CPTableViewSelectionDidChangeNotification
4978  object:self
4979  userInfo:nil];
4980 }
4981 
4985 - (void)becomeKeyWindow
4986 {
4987  [self setNeedsDisplay:YES];
4988 }
4989 
4993 - (void)resignKeyWindow
4994 {
4995  [self setNeedsDisplay:YES];
4996 }
4997 
5001 - (BOOL)becomeFirstResponder
5002 {
5003  [self setNeedsDisplay:YES];
5004  return YES;
5005 }
5006 
5010 - (BOOL)resignFirstResponder
5011 {
5012  [self setNeedsDisplay:YES];
5013  return YES;
5014 }
5015 
5019 - (BOOL)acceptsFirstResponder
5020 {
5021  return YES;
5022 }
5023 
5027 - (BOOL)needsPanelToBecomeKey
5028 {
5029  return YES;
5030 }
5031 
5035 - (CPView)hitTest:(CGPoint)aPoint
5036 {
5037  var hit = [super hitTest:aPoint];
5038 
5039  if ([[CPApp currentEvent] type] == CPLeftMouseDown && [hit acceptsFirstResponder] && ![self isRowSelected:[self rowForView:hit]])
5040  return self;
5041 
5042  return hit;
5043 }
5044 
5045 - (void)_startObservingFirstResponder
5046 {
5047  [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_firstResponderDidChange:) name:_CPWindowDidChangeFirstResponderNotification object:[self window]];
5048 }
5049 
5050 - (void)_stopObservingFirstResponder
5051 {
5052  [[CPNotificationCenter defaultCenter] removeObserver:self name:_CPWindowDidChangeFirstResponderNotification object:[self window]];
5053 }
5054 
5055 - (void)_firstResponderDidChange:(CPNotification)aNotification
5056 {
5057  var responder = [[self window] firstResponder];
5058 
5059  if (![responder isKindOfClass:[CPView class]] || ![responder isDescendantOf:self])
5060  {
5061  _editingRow = CPNotFound;
5062  _editingColumn = CPNotFound;
5063  return;
5064  }
5065 
5066  _editingRow = [self rowForView:responder];
5067  _editingColumn = [self columnForView:responder];
5068 
5069  if (_editingRow !== CPNotFound && [responder isKindOfClass:[CPTextField class]] && ![responder isBezeled])
5070  {
5071  [responder setBezeled:YES];
5072  [self _registerForEndEditingNote:responder];
5073  }
5074 }
5075 
5076 - (void)_registerForEndEditingNote:(CPView)aTextField
5077 {
5078  [[CPNotificationCenter defaultCenter] addObserver:self selector:@selector(_textFieldEditingDidEnd:) name:CPControlTextDidEndEditingNotification object:aTextField];
5079 }
5080 
5081 - (void)_unregisterForEndEditingNote:(CPView)aTextField
5082 {
5083  [[CPNotificationCenter defaultCenter] removeObserver:self name:CPControlTextDidEndEditingNotification object:aTextField];
5084 }
5085 
5086 - (void)_textFieldEditingDidEnd:(CPNotification)aNote
5087 {
5088  // FIXME: When you edit a text field and hit enter without any text modification, the CPControlTextDidEndEditingNotification
5089  // is NOT sent. This is a bug in CPTextField or CPControl according to cocoa.
5090  var textField = [aNote object];
5091 
5092  [self _unregisterForEndEditingNote:textField];
5093  [textField setBezeled:NO];
5094 
5095  var action = [self _disableActionIfExists:textField];
5096  [textField resignFirstResponder];
5097  [textField setAction:action];
5098 }
5099 
5100 - (SEL)_disableActionIfExists:(CPView)aView
5101 {
5102  // TODO: We disable action to prevent it from beeing sent twice when we resign the FR inside a textEndEditing notification.
5103  // Check if this is due to a bug in CPTextField.
5104  var action = nil;
5105  if ([aView respondsToSelector:@selector(action)] && (action = [aView action]))
5106  [aView setAction:nil];
5107 
5108  return action;
5109 }
5110 
5114 - (void)keyDown:(CPEvent)anEvent
5115 {
5116  var character = [anEvent charactersIgnoringModifiers],
5117  modifierFlags = [anEvent modifierFlags];
5118 
5119  // Check for the key events manually, as opposed to waiting for CPWindow to sent the actual action message
5120  // in _processKeyboardUIKey:, because we might not want to handle the arrow events.
5121  if (character === CPUpArrowFunctionKey || character === CPDownArrowFunctionKey)
5122  {
5123  // We're not interested in the arrow keys if there are no rows.
5124  // Technically we should also not be interested if we can't scroll,
5125  // but Cocoa doesn't handle that situation either.
5126  if ([self numberOfRows] !== 0)
5127  {
5128  [self _moveSelectionWithEvent:anEvent upward:(character === CPUpArrowFunctionKey)];
5129 
5130  return;
5131  }
5132  }
5133  else if (character === CPDeleteCharacter || character === CPDeleteFunctionKey)
5134  {
5135  // Don't call super if the delegate is interested in the delete key
5136  if ([self _sendDelegateDeleteKeyPressed])
5137  return;
5138  }
5139 
5140  [super keyDown:anEvent];
5141 }
5142 
5148 - (BOOL)_selectionIsBroken
5149 {
5150  return [self selectedRowIndexes]._ranges.length !== 1;
5151 }
5152 
5158 - (void)_moveSelectionWithEvent:(CPEvent)theEvent upward:(BOOL)shouldGoUpward
5159 {
5160  if (![self _sendDelegateSelectionShouldChangeInTableView])
5161  return;
5162 
5163  var selectedIndexes = [self selectedRowIndexes];
5164 
5165  if ([selectedIndexes count] > 0)
5166  {
5167  var extend = (([theEvent modifierFlags] & CPShiftKeyMask) && _allowsMultipleSelection),
5168  i = [self selectedRow];
5169 
5170  if ([self _selectionIsBroken])
5171  {
5172  while ([selectedIndexes containsIndex:i])
5173  {
5174  shouldGoUpward ? i-- : i++;
5175  }
5176  _wasSelectionBroken = true;
5177  }
5178  else if (_wasSelectionBroken && ((shouldGoUpward && i !== [selectedIndexes firstIndex]) || (!shouldGoUpward && i !== [selectedIndexes lastIndex])))
5179  {
5180  shouldGoUpward ? i = [selectedIndexes firstIndex] - 1 : i = [selectedIndexes lastIndex];
5181  _wasSelectionBroken = false;
5182  }
5183  else
5184  {
5185  shouldGoUpward ? i-- : i++;
5186  }
5187  }
5188  else
5189  {
5190  var extend = NO;
5191  //no rows are currently selected
5192  if ([self numberOfRows] > 0)
5193  var i = shouldGoUpward ? [self numberOfRows] - 1 : 0; // if we select upward select the last row, otherwise select the first row
5194  }
5195 
5196  if (i >= [self numberOfRows] || i < 0)
5197  return;
5198 
5199  if (![self _delegateRespondsToSelectionIndexesForProposedSelection] && [self _delegateRespondsToShouldSelectRow])
5200  {
5201  var shouldSelect = [self _sendDelegateShouldSelectRow:i];
5202 
5203  /* If shouldSelect returns NO it means this row cannot be selected.
5204  The proper behaviour is to then try to see if the next/previous
5205  row(s) can be selected, until we hit the first one that can be.
5206  */
5207  while (!shouldSelect && (i < [self numberOfRows] && i > 0))
5208  {
5209  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.
5210  shouldSelect = [self _sendDelegateShouldSelectRow:i];
5211  }
5212 
5213  if (!shouldSelect)
5214  return;
5215  }
5216 
5217  // If we go upward and see that this row is already selected we should deselect the row below.
5218  if (extend && [selectedIndexes containsIndex:i])
5219  {
5220  // The row we're on is the last to be selected.
5221  var differedLastSelectedRow = i;
5222 
5223  // no remove the one before/after it
5224  shouldGoUpward ? i++ : i--;
5225 
5226  [selectedIndexes removeIndex:i];
5227 
5228  //we're going to replace the selection
5229  extend = NO;
5230  }
5231  else if (extend)
5232  {
5233  if ([selectedIndexes containsIndex:i])
5234  {
5235  i = shouldGoUpward ? [selectedIndexes firstIndex] -1 : [selectedIndexes lastIndex] + 1;
5236  i = MIN(MAX(i, 0), [self numberOfRows] - 1);
5237  }
5238 
5239  [selectedIndexes addIndex:i];
5240  var differedLastSelectedRow = i;
5241  }
5242  else
5243  {
5244  selectedIndexes = [CPIndexSet indexSetWithIndex:i];
5245  var differedLastSelectedRow = i;
5246  }
5247 
5248  selectedIndexes = [self _sendDelegateSelectionIndexesForProposedSelection:selectedIndexes];
5249 
5250  [self selectRowIndexes:selectedIndexes byExtendingSelection:extend];
5251 
5252  // we differ because selectRowIndexes: does its own thing which would set the wrong index
5253  _lastSelectedRow = differedLastSelectedRow;
5254 
5255  if (i !== CPNotFound)
5256  [self scrollRowToVisible:i];
5257 }
5258 
5259 @end
5260 
5261 
5262 @implementation CPTableView (TableViewDataSource)
5263 
5268 - (BOOL)_dataSourceRespondsToObjectValueForTableColumn
5269 {
5270  return _implementedDataSourceMethods & CPTableViewDataSource_tableView_objectValueForTableColumn_row_;
5271 }
5272 
5277 - (BOOL)_dataSourceRespondsToWriteRowsWithIndexesToPasteboard
5278 {
5279  return _implementedDataSourceMethods & CPTableViewDataSource_tableView_writeRowsWithIndexes_toPasteboard_;
5280 }
5281 
5286 - (BOOL)_dataSourceRespondsToSetObjectValueForTableColumnRow
5287 {
5288  return CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_;
5289 }
5290 
5295 - (BOOL)_dataSourceRespondsToValidateDropProposedRowProposedDropOperation
5296 {
5297  return _implementedDataSourceMethods & CPTableViewDataSource_tableView_validateDrop_proposedRow_proposedDropOperation_;
5298 }
5299 
5304 - (BOOL)_dataSourceRespondsToNumberOfRowsinTableView
5305 {
5306  return _implementedDataSourceMethods & CPTableViewDataSource_numberOfRowsInTableView_;
5307 }
5308 
5314 - (int)_sendDataSourceNumberOfRowsInTableView
5315 {
5316  if (!(_implementedDataSourceMethods & CPTableViewDataSource_numberOfRowsInTableView_))
5317  return 0;
5318 
5319  return [_dataSource numberOfRowsInTableView:self];
5320 }
5321 
5327 - (id)_sendDataSourceObjectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5328 {
5329  if (!(_implementedDataSourceMethods & CPTableViewDataSource_tableView_objectValueForTableColumn_row_))
5330  return nil;
5331 
5332  return [_dataSource tableView:self objectValueForTableColumn:aTableColumn row:aRowIndex];
5333 }
5334 
5339 - (void)_sendDataSourceSetObjectValue:(id)anObject forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5340 {
5341  if (!(_implementedDataSourceMethods & CPTableViewDataSource_tableView_setObjectValue_forTableColumn_row_))
5342  return;
5343 
5344  [_dataSource tableView:self setObjectValue:anObject forTableColumn:aTableColumn row:aRowIndex];
5345 }
5346 
5351 - (void)_sendDataSourceSortDescriptorsDidChange:(CPArray)descriptors
5352 {
5353  if (!(_implementedDataSourceMethods & CPTableViewDataSource_tableView_sortDescriptorsDidChange_))
5354  return;
5355 
5356  [_dataSource tableView:self sortDescriptorsDidChange:descriptors];
5357 }
5358 
5363 - (BOOL)_sendDataSourceAcceptDrop:(id)info row:(CPInteger)aRowIndex dropOperation:(CPTableViewDropOperation)operation
5364 {
5365  if (!(_implementedDataSourceMethods & CPTableViewDataSource_tableView_acceptDrop_row_dropOperation_))
5366  return NO;
5367 
5368  return [_dataSource tableView:self acceptDrop:info row:aRowIndex dropOperation:operation];
5369 }
5370 
5375 - (CPDragOperation)_sendDataSourceValidateDrop:(id)info proposedRow:(CPInteger)aRowIndex proposedDropOperation:(CPTableViewDropOperation)operation
5376 {
5377  if (!(_implementedDataSourceMethods & CPTableViewDataSource_tableView_validateDrop_proposedRow_proposedDropOperation_))
5378  return CPDragOperationNone;
5379 
5380  return [_dataSource tableView:self validateDrop:info proposedRow:aRowIndex proposedDropOperation:operation];
5381 }
5382 
5387 - (BOOL)_sendDataSourceWriteRowsWithIndexes:(CPIndexSet)rowIndexes toPasteboard:(CPPasteboard)pboard
5388 {
5389  if (!(_implementedDataSourceMethods & CPTableViewDataSource_tableView_writeRowsWithIndexes_toPasteboard_))
5390  return NO;
5391 
5392  return [_dataSource tableView:self writeRowsWithIndexes:rowIndexes toPasteboard:pboard];
5393 }
5394 
5395 /*
5396  This method is sent to the data source for convenience...
5397 */
5398 - (void)draggedImage:(CPImage)anImage endedAt:(CGPoint)aLocation operation:(CPDragOperation)anOperation
5399 {
5400  if ([_dataSource respondsToSelector:@selector(tableView:didEndDraggedImage:atPosition:operation:)])
5401  [_dataSource tableView:self didEndDraggedImage:anImage atPosition:aLocation operation:anOperation];
5402 }
5403 
5404 
5405 #pragma mark -
5406 #pragma mark DataSource methods to implement
5407 
5412 - (CPArray)_sendDataSourceNamesOfPromisedFilesDroppedAtDestination:(CPURL)dropDestination forDraggedRowsWithIndexes:(CPIndexSet)indexSet
5413 {
5414  if (!(_implementedDataSourceMethods & CPTableViewDataSource_tableView_namesOfPromisedFilesDroppedAtDestination_forDraggedRowsWithIndexes_))
5415  return [];
5416 
5417  return [_dataSource tableView:self namesOfPromisedFilesDroppedAtDestination:dropDestination forDraggedRowsWithIndexes:indexSet];
5418 }
5419 
5420 @end
5421 
5422 
5423 @implementation CPTableView (TableViewDelegate)
5424 
5429 - (BOOL)_delegateRespondsToDataViewForTableColumn
5430 {
5431  return _implementedDelegateMethods & CPTableViewDelegate_tableView_dataViewForTableColumn_row_;
5432 }
5433 
5438 - (BOOL)_delegateRespondsToViewForTableColumn
5439 {
5440  return _implementedDelegateMethods & CPTableViewDelegate_tableView_viewForTableColumn_row_;
5441 }
5442 
5447 - (BOOL)_delegateRespondsToShouldSelectRow
5448 {
5449  return _implementedDelegateMethods & CPTableViewDelegate_tableView_shouldSelectRow_;
5450 }
5451 
5456 - (BOOL)_delegateRespondsToSelectionShouldChangeInTableView
5457 {
5458  return _implementedDelegateMethods & CPTableViewDelegate_selectionShouldChangeInTableView_;
5459 }
5460 
5465 - (BOOL)_delegateRespondsToSelectionIndexesForProposedSelection
5466 {
5467  return _implementedDelegateMethods & CPTableViewDelegate_tableView_selectionIndexesForProposedSelection_;
5468 }
5469 
5474 - (BOOL)_delegateRespondsToMenuForTableColumnRow
5475 {
5476  return _implementedDelegateMethods & CPTableViewDelegate_tableViewMenuForTableColumn_row_;
5477 }
5478 
5483 - (void)_sendDelegateDidClickTableColumn:(CPInteger)column
5484 {
5485  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_didClickTableColumn_)
5486  [_delegate tableView:self didClickTableColumn:_tableColumns[column]];
5487 }
5488 
5493 - (void)_sendDelegateDidDragTableColumn:(CPInteger)column
5494 {
5495  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_didDragTableColumn_)
5496  [_delegate tableView:self didDragTableColumn:_tableColumns[column]];
5497 }
5498 
5503 - (void)_sendDelegateMouseDownInHeaderOfTableColumn:(CPInteger)column
5504 {
5505  if (_implementedDelegateMethods & CPTableViewDelegate_tableView_mouseDownInHeaderOfTableColumn_)
5506  [_delegate tableView:self mouseDownInHeaderOfTableColumn:_tableColumns[column]];
5507 }
5508 
5509 /*
5510  @ignore
5511  Call the delegate tableViewDeleteKeyPressed
5512 */
5513 - (BOOL)_sendDelegateDeleteKeyPressed
5514 {
5515  if ([_delegate respondsToSelector: @selector(tableViewDeleteKeyPressed:)])
5516  {
5517  [_delegate tableViewDeleteKeyPressed:self];
5518  return YES;
5519  }
5520 
5521  return NO;
5522 }
5523 
5528 - (BOOL)_sendDelegateSelectionShouldChangeInTableView
5529 {
5530  if (!(_implementedDelegateMethods & CPTableViewDelegate_selectionShouldChangeInTableView_))
5531  return YES;
5532 
5533  return [_delegate selectionShouldChangeInTableView:self];
5534 }
5535 
5540 - (BOOL)_sendDelegateIsGroupRow:(CPInteger)anIndex
5541 {
5542  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_isGroupRow_))
5543  return NO;
5544 
5545  return [_delegate tableView:self isGroupRow:anIndex];
5546 }
5547 
5552 - (BOOL)_sendDelegateShouldSelectRow:(CPInteger)anIndex
5553 {
5554  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldSelectRow_))
5555  return YES;
5556 
5557  return [_delegate tableView:self shouldSelectRow:anIndex];
5558 }
5559 
5564 - (void)_sendDelegateWillDisplayView:(id)aCell forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5565 {
5566  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_willDisplayView_forTableColumn_row_))
5567  return;
5568 
5569  [_delegate tableView:self willDisplayView:aCell forTableColumn:aTableColumn row:aRowIndex];
5570 }
5571 
5576 - (CPMenu)_sendDelegateMenuForTableColumn:(CPTableColumn)aTableColumn row:aRowIndex
5577 {
5578  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableViewMenuForTableColumn_row_))
5579  return nil;
5580 
5581  return [_delegate tableView:self menuForTableColumn:aTableColumn row:aRowIndex];
5582 }
5583 
5584 /*
5585  @ignore
5586  Returns YES if the column at columnIndex can be reordered.
5587  It can be possible if column reordering is allowed and if the tableview
5588  delegate also accept the reordering
5589 */
5590 - (BOOL)_sendDelegateShouldReorderColumn:(CPInteger)columnIndex toColumn:(CPInteger)newColumnIndex
5591 {
5592  if ([self allowsColumnReordering] &&
5593  _implementedDelegateMethods & CPTableViewDelegate_tableView_shouldReorderColumn_toColumn_)
5594  {
5595  return [_delegate tableView:self shouldReorderColumn:columnIndex toColumn:newColumnIndex];
5596  }
5597 
5598  return [self allowsColumnReordering];
5599 }
5600 
5605 - (float)_sendDelegateHeightOfRow:(CPInteger)anIndex
5606 {
5607  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_heightOfRow_))
5608  return [self rowHeight];
5609 
5610  return [_delegate tableView:self heightOfRow:anIndex];
5611 }
5612 
5617 - (BOOL)_sendDelegateShouldEditTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5618 {
5619  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldEditTableColumn_row_))
5620  return YES;
5621 
5622  return [_delegate tableView:self shouldEditTableColumn:aTableColumn row:aRowIndex];
5623 }
5624 
5629 - (CPIndexSet)_sendDelegateSelectionIndexesForProposedSelection:(CPIndexSet)anIndexSet
5630 {
5631  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_selectionIndexesForProposedSelection_))
5632  return anIndexSet;
5633 
5634  return [_delegate tableView:self selectionIndexesForProposedSelection:anIndexSet];
5635 }
5636 
5641 - (CPView)_sendDelegateViewForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5642 {
5643  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_viewForTableColumn_row_))
5644  return nil;
5645 
5646  return [_delegate tableView:self viewForTableColumn:aTableColumn row:aRowIndex];
5647 }
5648 
5653 - (CPView)_sendDelegateDataViewForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5654 {
5655  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_dataViewForTableColumn_row_))
5656  return nil;
5657 
5658  return [_delegate tableView:self dataViewForTableColumn:aTableColumn row:aRowIndex];
5659 }
5660 
5661 
5662 #pragma mark -
5663 #pragma mark Delegate methods to implement
5664 
5669 - (BOOL)_sendDelegateShouldSelectTableColumn:(CPTableColumn)aTableColumn
5670 {
5671  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldSelectTableColumn_))
5672  return YES;
5673 
5674  return [_delegate tableView:self shouldSelectTableColumn:aTableColumn];
5675 }
5676 
5681 - (CPString)_sendDelegateToolTipForView:(id)aView rect:(CGRect)aRect tableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex mouseLocation:(CGPoint)aPoint
5682 {
5683  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_toolTipForView_rect_tableColumn_row_mouseLocation_))
5684  return nil;
5685 
5686  return [_delegate tableView:self toolTipForView:aView rect:aRect tableColumn:aTableColumn row:aRowIndex mouseLocation:aPoint];
5687 }
5688 
5693 - (BOOL)_sendDelegateShouldTrackView:(id)aView forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5694 {
5695  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldTrackView_forTableColumn_row_))
5696  return YES;
5697 
5698  return [_delegate tableView:self shouldTrackView:aView forTableColumn:aTableColumn row:aRowIndex];
5699 }
5700 
5705 - (BOOL)_sendDelegateShouldShowViewExpansionForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5706 {
5707  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldShowViewExpansionForTableColumn_row_))
5708  return YES;
5709 
5710  return [_delegate tableView:self shouldShowViewExpansionForTableColumn:aTableColumn row:aRowIndex];
5711 }
5712 
5717 - (BOOL)_sendDelegateShouldTypeSelectForEvent:(CPEvent)anEvent withCurrentSearchString:(CPString)aString
5718 {
5719  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_shouldTypeSelectForEvent_withCurrentSearchString_))
5720  return NO;
5721 
5722  return [_delegate tableView:self shouldTypeSelectForEvent:anEvent withCurrentSearchString:aString];
5723 }
5724 
5729 - (CPString)_sendDelegateTypeSelectStringForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
5730 {
5731  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_typeSelectStringForTableColumn_row_))
5732  return nil;
5733 
5734  return [_delegate tableView:self typeSelectStringForTableColumn:aTableColumn row:aRowIndex];
5735 }
5736 
5741 - (int)_sendDelegateNextTypeSelectMatchFromRow:(CPInteger)aRowIndex toRow:(CPInteger)aSecondRowIndex forString:(CPString)aString
5742 {
5743  if (!(_implementedDelegateMethods & CPTableViewDelegate_tableView_nextTypeSelectMatchFromRow_toRow_forString_))
5744  return -1;
5745 
5746  return [_delegate tableView:self nextTypeSelectMatchFromRow:aRowIndex toRow:aSecondRowIndex forString:aString];
5747 }
5748 
5749 @end
5750 
5751 @implementation CPTableView (Bindings)
5752 
5753 + (Class)_binderClassForBinding:(CPString)aBinding
5754 {
5755  if (aBinding == @"content")
5756  return [CPTableContentBinder class];
5757 
5758  return [super _binderClassForBinding:aBinding];
5759 }
5760 
5764 - (CPString)_replacementKeyPathForBinding:(CPString)aBinding
5765 {
5766  if (aBinding === @"selectionIndexes")
5767  return @"selectedRowIndexes";
5768 
5769  return [super _replacementKeyPathForBinding:aBinding];
5770 }
5771 
5775 - (void)_establishBindingsIfUnbound:(id)destination
5776 {
5777  if ([[self infoForBinding:@"content"] objectForKey:CPObservedObjectKey] !== destination)
5778  {
5779  [super bind:@"content" toObject:destination withKeyPath:@"arrangedObjects" options:nil];
5780  _contentBindingExplicitlySet = NO;
5781  }
5782 
5783  // If the content binding was set manually assume the user is taking manual control of establishing bindings.
5784  if (!_contentBindingExplicitlySet)
5785  {
5786  if ([[self infoForBinding:@"selectionIndexes"] objectForKey:CPObservedObjectKey] !== destination)
5787  [self bind:@"selectionIndexes" toObject:destination withKeyPath:@"selectionIndexes" options:nil];
5788 
5789  if ([[self infoForBinding:@"sortDescriptors"] objectForKey:CPObservedObjectKey] !== destination)
5790  [self bind:@"sortDescriptors" toObject:destination withKeyPath:@"sortDescriptors" options:nil];
5791  }
5792 }
5793 
5794 - (void)bind:(CPString)aBinding toObject:(id)anObject withKeyPath:(CPString)aKeyPath options:(CPDictionary)options
5795 {
5796  [super bind:aBinding toObject:anObject withKeyPath:aKeyPath options:options];
5797 
5798  if (aBinding == @"content")
5799  _contentBindingExplicitlySet = YES;
5800 }
5801 
5802 @end
5803 
5804 
5805 @implementation CPTableContentBinder : CPBinder
5806 {
5807  id _content;
5808 }
5809 
5810 - (void)setValueFor:(CPString)aBinding
5811 {
5812  var destination = [_info objectForKey:CPObservedObjectKey],
5813  keyPath = [_info objectForKey:CPObservedKeyPathKey];
5814 
5815  _content = [destination valueForKey:keyPath];
5816 
5817  [_source reloadData];
5818 }
5819 
5820 @end
5821 
5822 
5823 var CPTableViewDataSourceKey = @"CPTableViewDataSourceKey",
5824  CPTableViewDelegateKey = @"CPTableViewDelegateKey",
5825  CPTableViewHeaderViewKey = @"CPTableViewHeaderViewKey",
5826  CPTableViewTableColumnsKey = @"CPTableViewTableColumnsKey",
5827  CPTableViewRowHeightKey = @"CPTableViewRowHeightKey",
5828  CPTableViewIntercellSpacingKey = @"CPTableViewIntercellSpacingKey",
5829  CPTableViewSelectionHighlightStyleKey = @"CPTableViewSelectionHighlightStyleKey",
5830  CPTableViewMultipleSelectionKey = @"CPTableViewMultipleSelectionKey",
5831  CPTableViewEmptySelectionKey = @"CPTableViewEmptySelectionKey",
5832  CPTableViewColumnReorderingKey = @"CPTableViewColumnReorderingKey",
5833  CPTableViewColumnResizingKey = @"CPTableViewColumnResizingKey",
5834  CPTableViewColumnSelectionKey = @"CPTableViewColumnSelectionKey",
5835  CPTableViewColumnAutoresizingStyleKey = @"CPTableViewColumnAutoresizingStyleKey",
5836  CPTableViewGridColorKey = @"CPTableViewGridColorKey",
5837  CPTableViewGridStyleMaskKey = @"CPTableViewGridStyleMaskKey",
5838  CPTableViewUsesAlternatingBackgroundKey = @"CPTableViewUsesAlternatingBackgroundKey",
5839  CPTableViewAlternatingRowColorsKey = @"CPTableViewAlternatingRowColorsKey",
5840  CPTableViewHeaderViewKey = @"CPTableViewHeaderViewKey",
5841  CPTableViewCornerViewKey = @"CPTableViewCornerViewKey",
5842  CPTableViewAutosaveNameKey = @"CPTableViewAutosaveNameKey",
5843  CPTableViewArchivedReusableViewsKey = @"CPTableViewArchivedReusableViewsKey";
5844 
5845 @implementation CPTableView (CPCoding)
5846 
5847 - (id)initWithCoder:(CPCoder)aCoder
5848 {
5849  self = [super initWithCoder:aCoder];
5850 
5851  if (self)
5852  {
5853  //Configuring Behavior
5854  _allowsColumnReordering = [aCoder decodeBoolForKey:CPTableViewColumnReorderingKey];
5855  _allowsColumnResizing = [aCoder decodeBoolForKey:CPTableViewColumnResizingKey];
5856  _allowsMultipleSelection = [aCoder decodeBoolForKey:CPTableViewMultipleSelectionKey];
5857  _allowsEmptySelection = [aCoder decodeBoolForKey:CPTableViewEmptySelectionKey];
5858  _allowsColumnSelection = [aCoder decodeBoolForKey:CPTableViewColumnSelectionKey];
5859 
5860  //Setting Display Attributes
5861  _selectionHighlightStyle = [aCoder decodeIntForKey:CPTableViewSelectionHighlightStyleKey];
5862  _columnAutoResizingStyle = [aCoder decodeIntForKey:CPTableViewColumnAutoresizingStyleKey];
5863 
5864  _tableColumns = [aCoder decodeObjectForKey:CPTableViewTableColumnsKey] || [];
5865  [_tableColumns makeObjectsPerformSelector:@selector(setTableView:) withObject:self];
5866 
5867  _rowHeight = [aCoder decodeFloatForKey:CPTableViewRowHeightKey] || [self valueForThemeAttribute:@"default-row-height"];
5868  _intercellSpacing = [aCoder decodeSizeForKey:CPTableViewIntercellSpacingKey];
5869 
5870  if (CGSizeEqualToSize(_intercellSpacing, CGSizeMakeZero()))
5871  _intercellSpacing = CGSizeMake(3.0, 2.0);
5872 
5873  [self setGridColor:[aCoder decodeObjectForKey:CPTableViewGridColorKey]];
5874  _gridStyleMask = [aCoder decodeIntForKey:CPTableViewGridStyleMaskKey];
5875 
5876  _usesAlternatingRowBackgroundColors = [aCoder decodeObjectForKey:CPTableViewUsesAlternatingBackgroundKey];
5877  [self setAlternatingRowBackgroundColors:[aCoder decodeObjectForKey:CPTableViewAlternatingRowColorsKey]];
5878 
5879  _headerView = [aCoder decodeObjectForKey:CPTableViewHeaderViewKey];
5880  _cornerView = [aCoder decodeObjectForKey:CPTableViewCornerViewKey];
5881 
5882  [self setDataSource:[aCoder decodeObjectForKey:CPTableViewDataSourceKey]];
5883  [self setDelegate:[aCoder decodeObjectForKey:CPTableViewDelegateKey]];
5884 
5885  [self _init];
5886 
5887  if ([aCoder containsValueForKey:CPTableViewArchivedReusableViewsKey])
5888  _archivedDataViews = [aCoder decodeObjectForKey:CPTableViewArchivedReusableViewsKey];
5889 
5890  [self _updateIsViewBased];
5891 
5892  [self viewWillMoveToSuperview:[self superview]];
5893 
5894  // Do this as late as possible to make sure the tableview is fully configured
5895  [self setAutosaveName:[aCoder decodeObjectForKey:CPTableViewAutosaveNameKey]];
5896  }
5897 
5898  return self;
5899 }
5900 
5901 - (void)encodeWithCoder:(CPCoder)aCoder
5902 {
5903  [super encodeWithCoder:aCoder];
5904 
5905  [aCoder encodeObject:_dataSource forKey:CPTableViewDataSourceKey];
5906  [aCoder encodeObject:_delegate forKey:CPTableViewDelegateKey];
5907 
5908  [aCoder encodeFloat:_rowHeight forKey:CPTableViewRowHeightKey];
5909  [aCoder encodeSize:_intercellSpacing forKey:CPTableViewIntercellSpacingKey];
5910 
5911  [aCoder encodeInt:_selectionHighlightStyle forKey:CPTableViewSelectionHighlightStyleKey];
5912  [aCoder encodeInt:_columnAutoResizingStyle forKey:CPTableViewColumnAutoresizingStyleKey];
5913 
5914  [aCoder encodeBool:_allowsMultipleSelection forKey:CPTableViewMultipleSelectionKey];
5915  [aCoder encodeBool:_allowsEmptySelection forKey:CPTableViewEmptySelectionKey];
5916  [aCoder encodeBool:_allowsColumnReordering forKey:CPTableViewColumnReorderingKey];
5917  [aCoder encodeBool:_allowsColumnResizing forKey:CPTableViewColumnResizingKey];
5918  [aCoder encodeBool:_allowsColumnSelection forKey:CPTableViewColumnSelectionKey];
5919 
5920  [aCoder encodeObject:_tableColumns forKey:CPTableViewTableColumnsKey];
5921 
5922  [aCoder encodeObject:[self gridColor] forKey:CPTableViewGridColorKey];
5923  [aCoder encodeInt:_gridStyleMask forKey:CPTableViewGridStyleMaskKey];
5924 
5925  [aCoder encodeBool:_usesAlternatingRowBackgroundColors forKey:CPTableViewUsesAlternatingBackgroundKey];
5926  [aCoder encodeObject:[self alternatingRowBackgroundColors] forKey:CPTableViewAlternatingRowColorsKey];
5927 
5928  [aCoder encodeObject:_cornerView forKey:CPTableViewCornerViewKey];
5929  [aCoder encodeObject:_headerView forKey:CPTableViewHeaderViewKey];
5930 
5931  [aCoder encodeObject:_autosaveName forKey:CPTableViewAutosaveNameKey];
5932 
5933  if (_archivedDataViews)
5934  [aCoder encodeObject:_archivedDataViews forKey:CPTableViewArchivedReusableViewsKey];
5935 }
5936 
5937 @end
5938 
5939 
5940 @implementation CPIndexSet (tableview)
5941 
5942 - (void)removeMatches:(CPIndexSet)otherSet
5943 {
5944  var firstindex = [self firstIndex],
5945  index = MIN(firstindex, [otherSet firstIndex]),
5946  switchFlag = (index == firstindex);
5947 
5948  while (index != CPNotFound)
5949  {
5950  var indexSet = (switchFlag) ? otherSet : self,
5951  otherIndex = [indexSet indexGreaterThanOrEqualToIndex:index];
5952 
5953  if (otherIndex == index)
5954  {
5955  [self removeIndex:index];
5956  [otherSet removeIndex:index];
5957  }
5958 
5959  index = otherIndex;
5960  switchFlag = !switchFlag;
5961  }
5962 }
5963 
5964 @end
5965 
5966 @implementation _CPDropOperationDrawingView : CPView
5967 {
5968  unsigned dropOperation;
5969  CPTableView tableView;
5970  int currentRow;
5971  BOOL isBlinking;
5972 }
5973 
5974 - (void)drawRect:(CGRect)aRect
5975 {
5976  if (tableView._destinationDragStyle === CPTableViewDraggingDestinationFeedbackStyleNone || isBlinking)
5977  return;
5978 
5979  var context = [[CPGraphicsContext currentContext] graphicsPort],
5980  borderRadius,
5981  borderColor,
5982  borderWidth,
5983  backgroundColor;
5984 
5985  if (currentRow === -1)
5986  {
5987  borderColor = [tableView valueForThemeAttribute:@"dropview-on-border-color"];
5988  borderWidth = [tableView valueForThemeAttribute:@"dropview-on-border-width"];
5989 
5990  CGContextSetStrokeColor(context, borderColor);
5991  CGContextSetLineWidth(context, borderWidth);
5992  CGContextStrokeRect(context, [self bounds]);
5993  }
5994 
5995  else if (dropOperation === CPTableViewDropOn)
5996  {
5997  //if row is selected don't fill and stroke white
5998  var selectedRows = [tableView selectedRowIndexes],
5999  newRect = CGRectMake(aRect.origin.x + 2, aRect.origin.y + 2, aRect.size.width - 4, aRect.size.height - 5);
6000 
6001  if ([selectedRows containsIndex:currentRow])
6002  {
6003  borderRadius = [tableView valueForThemeAttribute:@"dropview-on-selected-border-radius"];
6004  borderColor = [tableView valueForThemeAttribute:@"dropview-on-selected-border-color"];
6005  borderWidth = [tableView valueForThemeAttribute:@"dropview-on-selected-border-width"];
6006  backgroundColor = [tableView valueForThemeAttribute:@"dropview-on-selected-background-color"];
6007  }
6008  else
6009  {
6010  borderRadius = [tableView valueForThemeAttribute:@"dropview-on-border-radius"];
6011  borderColor = [tableView valueForThemeAttribute:@"dropview-on-border-color"];
6012  borderWidth = [tableView valueForThemeAttribute:@"dropview-on-border-width"];
6013  backgroundColor = [tableView valueForThemeAttribute:@"dropview-on-background-color"];
6014  }
6015 
6016  CGContextSetStrokeColor(context, borderColor);
6017  CGContextSetLineWidth(context, borderWidth);
6018  CGContextSetFillColor(context, backgroundColor);
6019  CGContextFillRoundedRectangleInRect(context, newRect, borderRadius, YES, YES, YES, YES);
6020  CGContextStrokeRoundedRectangleInRect(context, newRect, borderRadius, YES, YES, YES, YES);
6021 
6022  }
6023  else if (dropOperation === CPTableViewDropAbove)
6024  {
6025  //reposition the view up a tad
6026  [self setFrameOrigin:CGPointMake(_frame.origin.x, _frame.origin.y - 8)];
6027 
6028  var selectedRows = [tableView selectedRowIndexes];
6029 
6030  if ([selectedRows containsIndex:currentRow - 1] || [selectedRows containsIndex:currentRow])
6031  {
6032  borderColor = [tableView valueForThemeAttribute:@"dropview-above-selected-border-color"];
6033  borderWidth = [tableView valueForThemeAttribute:@"dropview-above-selected-border-width"];
6034  }
6035  else
6036  {
6037  borderColor = [tableView valueForThemeAttribute:@"dropview-above-border-color"];
6038  borderWidth = [tableView valueForThemeAttribute:@"dropview-above-border-width"];
6039  }
6040 
6041  CGContextSetStrokeColor(context, borderColor);
6042  CGContextSetLineWidth(context, borderWidth);
6043  CGContextStrokeEllipseInRect(context, CGRectMake(aRect.origin.x + 4, aRect.origin.y + 4, 8, 8)); // circle
6044 
6045  CGContextBeginPath(context);
6046  CGContextMoveToPoint(context, 10, aRect.origin.y + 8);
6047  CGContextAddLineToPoint(context, aRect.size.width - aRect.origin.y - 8, aRect.origin.y + 8);
6048  CGContextStrokePath(context);
6049  }
6050 }
6051 
6052 - (void)blink
6053 {
6054  if (dropOperation !== CPTableViewDropOn)
6055  return;
6056 
6057  isBlinking = YES;
6058 
6059  var showCallback = function()
6060  {
6061  objj_msgSend(self, "setHidden:", NO)
6062  isBlinking = NO;
6063  };
6064 
6065  var hideCallback = function()
6066  {
6067  objj_msgSend(self, "setHidden:", YES)
6068  isBlinking = YES;
6069  };
6070 
6071  objj_msgSend(self, "setHidden:", YES);
6072  [CPTimer scheduledTimerWithTimeInterval:0.1 callback:showCallback repeats:NO];
6073  [CPTimer scheduledTimerWithTimeInterval:0.19 callback:hideCallback repeats:NO];
6074  [CPTimer scheduledTimerWithTimeInterval:0.27 callback:showCallback repeats:NO];
6075 }
6076 
6077 @end
6078 
6079 
6080 @implementation _CPColumnDragView : CPView
6081 {
6082  CPColor _lineColor;
6083 }
6084 
6085 - (id)initWithLineColor:(CPColor)aColor
6086 {
6087  self = [super initWithFrame:CGRectMakeZero()];
6088 
6089  if (self)
6090  _lineColor = aColor;
6091 
6092  return self;
6093 }
6094 
6095 - (void)drawRect:(CGRect)aRect
6096 {
6097  var context = [[CPGraphicsContext currentContext] graphicsPort];
6098 
6099  CGContextSetStrokeColor(context, _lineColor);
6100 
6101  var points = [
6102  CGPointMake(0.5, 0),
6103  CGPointMake(0.5, aRect.size.height)
6104  ];
6105 
6106  CGContextStrokeLineSegments(context, points, 2);
6107 
6108  points = [
6109  CGPointMake(aRect.size.width - 0.5, 0),
6110  CGPointMake(aRect.size.width - 0.5, aRect.size.height)
6111  ];
6112 
6113  CGContextStrokeLineSegments(context, points, 2);
6114 }
6115 
6116 @end
6117 
6118 
6119 @implementation CPTableCellView : CPView
6120 {
6121  id _objectValue;
6122 
6123  CPTextField _textField;
6124  CPImageView _imageView;
6125 }
6126 
6127 - (void)awakeFromCib
6128 {
6129  [self setThemeState:CPThemeStateTableDataView];
6130 }
6131 
6132 - (BOOL)setThemeState:(CPThemeState)aState
6133 {
6134  [super setThemeState:aState];
6135  [self recursivelyPerformSelector:@selector(setThemeState:) withObject:aState startingFrom:self];
6136 }
6137 
6138 - (BOOL)unsetThemeState:(CPThemeState)aState
6139 {
6140  [super unsetThemeState:aState];
6141  [self recursivelyPerformSelector:@selector(unsetThemeState:) withObject:aState startingFrom:self];
6142 }
6143 
6144 - (void)recursivelyPerformSelector:(SEL)selector withObject:(id)anObject startingFrom:(id)aView
6145 {
6146  // Avoid infinite loop if a subview is a CPTableCellView.[[aView subviews] enumerateObjectsUsingBlock:function(view, idx)
6147  {
6148  [view performSelector:selector withObject:anObject];
6149 
6150  if (![view isKindOfClass:[self class]])
6151  [self recursivelyPerformSelector:selector withObject:anObject startingFrom:view];
6152  }];
6153 }
6154 
6155 - (CPString)description
6156 {
6157  return "<" + [self className] + " 0x" + [CPString stringWithHash:[self UID]] + " identifier=" + [self identifier] + ">";
6158 }
6159 
6160 @end
6161 
6162 @implementation CPTableView (CPSynthesizedAccessors)
6163 
6167 - (BOOL)disableAutomaticResizing
6168 {
6169  return _disableAutomaticResizing;
6170 }
6171 
6175 - (void)setDisableAutomaticResizing:(BOOL)aValue
6176 {
6177  _disableAutomaticResizing = aValue;
6178 }
6179 
6180 @end
6181 
6182 @implementation CPTableContentBinder (CPSynthesizedAccessors)
6183 
6187 - (id)content
6188 {
6189  return _content;
6190 }
6191 
6195 - (void)setContent:(id)aValue
6196 {
6197  _content = aValue;
6198 }
6199 
6200 @end
6201 
6202 @implementation CPTableCellView (CPSynthesizedAccessors)
6203 
6207 - (id)objectValue
6208 {
6209  return _objectValue;
6210 }
6211 
6215 - (void)setObjectValue:(id)aValue
6216 {
6217  _objectValue = aValue;
6218 }
6219 
6223 - (CPTextField)textField
6224 {
6225  return _textField;
6226 }
6227 
6231 - (void)setTextField:(CPTextField)aValue
6232 {
6233  _textField = aValue;
6234 }
6235 
6239 - (CPImageView)imageView
6240 {
6241  return _imageView;
6242 }
6243 
6247 - (void)setImageView:(CPImageView)aValue
6248 {
6249  _imageView = aValue;
6250 }
6251 
6252 @end