API  0.9.7
 All Classes Files Functions Variables Macros Groups Pages
CPOutlineView.j
Go to the documentation of this file.
1 /*
2  * CPOutlineView.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 @global CPApp
25 
26 CPOutlineViewColumnDidMoveNotification = @"CPOutlineViewColumnDidMoveNotification";
27 CPOutlineViewColumnDidResizeNotification = @"CPOutlineViewColumnDidResizeNotification";
28 CPOutlineViewItemDidCollapseNotification = @"CPOutlineViewItemDidCollapseNotification";
29 CPOutlineViewItemDidExpandNotification = @"CPOutlineViewItemDidExpandNotification";
30 CPOutlineViewItemWillCollapseNotification = @"CPOutlineViewItemWillCollapseNotification";
31 CPOutlineViewItemWillExpandNotification = @"CPOutlineViewItemWillExpandNotification";
32 CPOutlineViewSelectionDidChangeNotification = @"CPOutlineViewSelectionDidChangeNotification";
33 CPOutlineViewSelectionIsChangingNotification = @"CPOutlineViewSelectionIsChangingNotification";
34 
38 
43 
46 
48 
50 
77 
79 
83 
84 #define SELECTION_SHOULD_CHANGE(anOutlineView) (!((anOutlineView)._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_selectionShouldChangeInOutlineView_) || [(anOutlineView)._outlineViewDelegate selectionShouldChangeInOutlineView:(anOutlineView)])
85 
86 #define SHOULD_SELECT_ITEM(anOutlineView, anItem) (!((anOutlineView)._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectItem_) || [(anOutlineView)._outlineViewDelegate outlineView:(anOutlineView) shouldSelectItem:(anItem)])
87 
103 @implementation CPOutlineView : CPTableView
104 {
105  id _outlineViewDataSource;
106  id _outlineViewDelegate;
107  CPTableColumn _outlineTableColumn;
108 
109  float _indentationPerLevel;
110  BOOL _indentationMarkerFollowsDataView;
111 
112  CPInteger _implementedOutlineViewDataSourceMethods;
113  CPInteger _implementedOutlineViewDelegateMethods;
114 
115  Object _rootItemInfo;
116  CPMutableArray _itemsForRows;
117  Object _itemInfosForItems;
118 
119  CPControl _disclosureControlPrototype;
120  CPArray _disclosureControlsForRows;
121  CPData _disclosureControlData;
122  CPArray _disclosureControlQueue;
123 
124  BOOL _shouldRetargetItem;
125  id _retargetedItem;
126 
127  BOOL _shouldRetargetChildIndex;
128  CPInteger _retargedChildIndex;
129  CPTimer _dragHoverTimer;
130  id _dropItem;
131 
132  BOOL _coalesceSelectionNotificationState;
133 }
134 
135 - (id)initWithFrame:(CGRect)aFrame
136 {
137  self = [super initWithFrame:aFrame];
138 
139  if (self)
140  {
141  _selectionHighlightStyle = CPTableViewSelectionHighlightStyleSourceList;
142 
143  // The root item has weight "0", thus represents the weight solely of its descendants.
144  _rootItemInfo = { isExpanded:YES, isExpandable:NO, shouldShowOutlineDisclosureControl:NO, level:-1, row:-1, children:[], weight:0 };
145 
146  _itemsForRows = [];
147  _itemInfosForItems = { };
148  _disclosureControlsForRows = [];
149 
150  _retargetedItem = nil;
151  _shouldRetargetItem = NO;
152 
153  _retargedChildIndex = nil;
154  _shouldRetargetChildIndex = NO;
155 
156  [self setIndentationPerLevel:16.0];
158 
159  [super setDataSource:[[_CPOutlineViewTableViewDataSource alloc] initWithOutlineView:self]];
160  [super setDelegate:[[_CPOutlineViewTableViewDelegate alloc] initWithOutlineView:self]];
161 
162  [self setDisclosureControlPrototype:[[CPDisclosureButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 10.0)]];
163  }
164 
165  return self;
166 }
218 - (void)setDataSource:(id)aDataSource
219 {
220  if (_outlineViewDataSource === aDataSource)
221  return;
222 
223  if (![aDataSource respondsToSelector:@selector(outlineView:child:ofItem:)])
224  [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:child:ofItem:'"];
225 
226  if (![aDataSource respondsToSelector:@selector(outlineView:isItemExpandable:)])
227  [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:isItemExpandable:'"];
228 
229  if (![aDataSource respondsToSelector:@selector(outlineView:numberOfChildrenOfItem:)])
230  [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:numberOfChildrenOfItem:'"];
231 
232  _outlineViewDataSource = aDataSource;
233  _implementedOutlineViewDataSourceMethods = 0;
234 
235  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:objectValueForTableColumn:byItem:)])
236  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_objectValue_forTableColumn_byItem_;
237 
238  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:setObjectValue:forTableColumn:byItem:)])
239  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_setObjectValue_forTableColumn_byItem_;
240 
241  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:shouldDeferDisplayingChildrenOfItem:)])
242  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_shouldDeferDisplayingChildrenOfItem_;
243 
244  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:acceptDrop:item:childIndex:)])
245  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_acceptDrop_item_childIndex_;
246 
247  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:validateDrop:proposedItem:proposedChildIndex:)])
249 
250  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:validateDrop:proposedRow:proposedDropOperation:)])
252 
253  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:namesOfPromisedFilesDroppedAtDestination:forDraggedItems:)])
255 
256  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:itemForPersistentObject:)])
257  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_itemForPersistentObject_;
258 
259  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:persistentObjectForItem:)])
260  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_persistentObjectForItem_;
261 
262  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:writeItems:toPasteboard:)])
263  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_writeItems_toPasteboard_;
264 
265  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:sortDescriptorsDidChange:)])
266  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_sortDescriptorsDidChange_;
267 
268  [self reloadData];
269 }
270 
276 - (id)dataSource
277 {
278  return _outlineViewDataSource;
279 }
280 
288 - (BOOL)isExpandable:(id)anItem
289 {
290  if (!anItem)
291  return YES;
292 
293  var itemInfo = _itemInfosForItems[[anItem UID]];
294 
295  if (!itemInfo)
296  return NO;
297 
298  return itemInfo.isExpandable;
299 }
300 
301 - (BOOL)_shouldShowOutlineDisclosureControlForItem:(id)anItem
302 {
303  if (!anItem)
304  return YES;
305 
306  var itemInfo = _itemInfosForItems[[anItem UID]];
307 
308  if (!itemInfo)
309  return YES;
310 
311  return itemInfo.shouldShowOutlineDisclosureControl;
312 }
313 
321 - (BOOL)isItemExpanded:(id)anItem
322 {
323  if (!anItem)
324  return YES;
325 
326  var itemInfo = _itemInfosForItems[[anItem UID]];
327 
328  if (!itemInfo)
329  return NO;
330 
331  return itemInfo.isExpanded;
332 }
333 
339 - (void)expandItem:(id)anItem
340 {
341  [self expandItem:anItem expandChildren:NO];
342 }
343 
350 - (void)expandItem:(id)anItem expandChildren:(BOOL)shouldExpandChildren
351 {
352  if ([self _delegateRespondsToShouldExpandItem])
353  if ([_outlineViewDelegate outlineView:self shouldExpandItem:anItem] == NO)
354  return;
355 
356  var itemInfo = null;
357 
358  if (!anItem)
359  itemInfo = _rootItemInfo;
360  else
361  itemInfo = _itemInfosForItems[[anItem UID]];
362 
363  if (!itemInfo)
364  return;
365 
366  // When shouldExpandChildren is YES, we need to make sure we're collecting
367  // selection notifications so that exactly one IsChanging and one
368  // DidChange is sent as needed, for the totality of the operation.
369  var isTopLevel = NO;
370  if (!_coalesceSelectionNotificationState)
371  {
372  isTopLevel = YES;
373  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOn;
374  }
375 
376  // To prevent items which are already expanded from firing notifications.
377  if (!itemInfo.isExpanded)
378  {
379  [self _noteItemWillExpand:anItem];
380 
381  var previousRowCount = [self numberOfRows];
382 
383  itemInfo.isExpanded = YES;
384  [self reloadItem:anItem reloadChildren:YES];
385  [self _noteItemDidExpand:anItem];
386 
387  // Shift selection indexes below so that the same items remain selected.
388  var rowCountDelta = [self numberOfRows] - previousRowCount;
389  if (rowCountDelta)
390  {
391  var selection = [self selectedRowIndexes],
392  expandIndex = [self rowForItem:anItem] + 1;
393 
394  if ([selection intersectsIndexesInRange:CPMakeRange(expandIndex, _itemsForRows.length)])
395  {
396  [self _noteSelectionIsChanging];
397  [selection shiftIndexesStartingAtIndex:expandIndex by:rowCountDelta];
398  [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
399  }
400  }
401  }
402 
403  if (shouldExpandChildren)
404  {
405  var children = itemInfo.children,
406  childIndex = children.length;
407 
408  while (childIndex--)
409  [self expandItem:children[childIndex] expandChildren:YES];
410  }
411 
412  if (isTopLevel)
413  {
414  var r = _coalesceSelectionNotificationState;
415  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOff;
417  [self _noteSelectionDidChange];
418  }
419 }
420 
426 - (void)collapseItem:(id)anItem
427 {
428  if (!anItem)
429  return;
430 
431  if ([self _delegateRespondsToShouldCollapseItem])
432  if ([_outlineViewDelegate outlineView:self shouldCollapseItem:anItem] == NO)
433  return;
434 
435  var itemInfo = _itemInfosForItems[[anItem UID]];
436 
437  if (!itemInfo)
438  return;
439 
440  if (!itemInfo.isExpanded)
441  return;
442 
443  // Don't spam notifications.
444  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOn;
445 
446  [self _noteItemWillCollapse:anItem];
447  // Update selections:
448  // * Deselect items inside the collapsed item.
449  // * Shift row selections below the collapsed item so that the same logical items remain selected.
450  var collapseTopIndex = [self rowForItem:anItem],
451  topLevel = [self levelForRow:collapseTopIndex],
452  collapseEndIndex = collapseTopIndex;
453 
454  while (collapseEndIndex + 1 < _itemsForRows.length && [self levelForRow:collapseEndIndex + 1] > topLevel)
455  collapseEndIndex++;
456 
457  var collapseRange = CPMakeRange(collapseTopIndex + 1, collapseEndIndex - collapseTopIndex);
458 
459  if (collapseRange.length)
460  {
461  var selection = [self selectedRowIndexes];
462 
463  if ([selection intersectsIndexesInRange:collapseRange])
464  {
465  [self _noteSelectionIsChanging];
466  [selection removeIndexesInRange:collapseRange];
467  [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
468  }
469 
470  // Shift any selected rows below upwards.
471  if ([selection intersectsIndexesInRange:CPMakeRange(collapseEndIndex + 1, _itemsForRows.length)])
472  {
473  [self _noteSelectionIsChanging];
474  [selection shiftIndexesStartingAtIndex:collapseEndIndex + 1 by:-collapseRange.length];
475  [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
476  }
477  }
478  itemInfo.isExpanded = NO;
479 
480  [self reloadItem:anItem reloadChildren:YES];
481  [self _noteItemDidCollapse:anItem];
482 
483  // Send selection notifications only after the items have loaded so that
484  // the new selection is consistent with the actual rows for any observers.
485  var r = _coalesceSelectionNotificationState;
486  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOff;
488  [self _noteSelectionDidChange];
489 }
490 
496 - (void)reloadItem:(id)anItem
497 {
498  [self reloadItem:anItem reloadChildren:NO];
499 }
500 
507 - (void)reloadItem:(id)anItem reloadChildren:(BOOL)shouldReloadChildren
508 {
509  if (!!shouldReloadChildren || !anItem)
510  _loadItemInfoForItem(self, anItem);
511  else
512  _reloadItem(self, anItem);
513 
514  [super reloadData];
515 }
516 
523 - (id)itemAtRow:(CPInteger)aRow
524 {
525  return _itemsForRows[aRow] || nil;
526 }
527 
534 - (CPInteger)rowForItem:(id)anItem
535 {
536  if (!anItem)
537  return _rootItemInfo.row;
538 
539  var itemInfo = _itemInfosForItems[[anItem UID]];
540 
541  if (!itemInfo)
542  return CPNotFound;
543 
544  return itemInfo.row;
545 }
546 
553 - (void)setOutlineTableColumn:(CPTableColumn)aTableColumn
554 {
555  if (_outlineTableColumn === aTableColumn)
556  return;
557 
558  _outlineTableColumn = aTableColumn;
559 
560  // FIXME: efficiency.
561  [self reloadData];
562 }
563 
569 - (CPTableColumn)outlineTableColumn
570 {
571  return _outlineTableColumn;
572 }
573 
582 - (CPInteger)levelForItem:(id)anItem
583 {
584  if (!anItem)
585  return _rootItemInfo.level;
586 
587  var itemInfo = _itemInfosForItems[[anItem UID]];
588 
589  if (!itemInfo)
590  return CPNotFound;
591 
592  return itemInfo.level;
593 }
594 
602 - (CPInteger)levelForRow:(CPInteger)aRow
603 {
604  return [self levelForItem:[self itemAtRow:aRow]];
605 }
606 
612 - (void)setIndentationPerLevel:(float)anIndentationWidth
613 {
614  if (_indentationPerLevel === anIndentationWidth)
615  return;
616 
617  _indentationPerLevel = anIndentationWidth;
618 
619  // FIXME: efficiency!!!!
620  [self reloadData];
621 }
622 
628 - (float)indentationPerLevel
629 {
630  return _indentationPerLevel;
631 }
632 
640 - (void)setIndentationMarkerFollowsDataView:(BOOL)indentationMarkerShouldFollowDataView
641 {
642  if (_indentationMarkerFollowsDataView === indentationMarkerShouldFollowDataView)
643  return;
644 
645  _indentationMarkerFollowsDataView = indentationMarkerShouldFollowDataView;
646 
647  // !!!!
648  [self reloadData];
649 }
650 
658 - (BOOL)indentationMarkerFollowsDataView
659 {
660  return _indentationMarkerFollowsDataView;
661 }
662 
670 - (id)parentForItem:(id)anItem
671 {
672  if (!anItem)
673  return nil;
674 
675  var itemInfo = _itemInfosForItems[[anItem UID]];
676 
677  if (!itemInfo)
678  return nil;
679 
680  var parent = itemInfo.parent;
681 
682  // Check if the parent is the root item because we never return the actual root item
683  if (itemInfo[[parent UID]] === _rootItemInfo)
684  parent = nil;
685 
686  return parent;
687 }
688 
694 - (CGRect)_frameOfOutlineDataViewAtRow:(CPInteger)aRow
695 {
696  var columnIndex = [[self tableColumns] indexOfObject:_outlineTableColumn],
697  frame = [super frameOfDataViewAtColumn:columnIndex row:aRow],
698  indentationWidth = ([self levelForRow:aRow] + 1) * [self indentationPerLevel];
699 
700  frame.origin.x += indentationWidth;
701  frame.size.width -= indentationWidth;
702 
703  return frame;
704 }
705 
714 - (CGRect)frameOfOutlineDisclosureControlAtRow:(CPInteger)aRow
715 {
716  var theItem = [self itemAtRow:aRow];
717  if (![self isExpandable:theItem] || ![self _shouldShowOutlineDisclosureControlForItem:theItem])
718  return CGRectMakeZero();
719 
720  var dataViewFrame = [self _frameOfOutlineDataViewAtRow:aRow],
721  disclosureWidth = CGRectGetWidth([_disclosureControlPrototype frame]),
722  frame = CGRectMake(CGRectGetMinX(dataViewFrame) - disclosureWidth, CGRectGetMinY(dataViewFrame), disclosureWidth, CGRectGetHeight(dataViewFrame));
723 
724  return frame;
725 }
726 
731 - (void)_setSelectedRowIndexes:(CPIndexSet)rows
732 {
733  if (_disclosureControlsForRows.length)
734  {
735  var indexes = [_selectedRowIndexes copy];
736  [indexes removeIndexesInRange:CPMakeRange(_disclosureControlsForRows.length, _itemsForRows.length - _disclosureControlsForRows.length)];
737  [[_disclosureControlsForRows objectsAtIndexes:indexes] makeObjectsPerformSelector:@selector(unsetThemeState:) withObject:CPThemeStateSelected];
738  }
739 
740  [super _setSelectedRowIndexes:rows];
741 
742  if (_disclosureControlsForRows.length)
743  {
744  var indexes = [_selectedRowIndexes copy];
745  [indexes removeIndexesInRange:CPMakeRange(_disclosureControlsForRows.length, _itemsForRows.length - _disclosureControlsForRows.length)];
746  [[_disclosureControlsForRows objectsAtIndexes:indexes] makeObjectsPerformSelector:@selector(setThemeState:) withObject:CPThemeStateSelected];
747  }
748 }
749 
826 - (void)setDelegate:(id)aDelegate
827 {
828  if (_outlineViewDelegate === aDelegate)
829  return;
830 
831  var defaultCenter = [CPNotificationCenter defaultCenter];
832 
833  if (_outlineViewDelegate)
834  {
835  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidMove:)])
836  [defaultCenter
837  removeObserver:_outlineViewDelegate
838  name:CPOutlineViewColumnDidMoveNotification
839  object:self];
840 
841  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidResize:)])
842  [defaultCenter
843  removeObserver:_outlineViewDelegate
844  name:CPOutlineViewColumnDidResizeNotification
845  object:self];
846 
847  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionDidChange:)])
848  [defaultCenter
849  removeObserver:_outlineViewDelegate
850  name:CPOutlineViewSelectionDidChangeNotification
851  object:self];
852 
853  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionIsChanging:)])
854  [defaultCenter
855  removeObserver:_outlineViewDelegate
856  name:CPOutlineViewSelectionIsChangingNotification
857  object:self];
858 
859  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillExpand:)])
860  [defaultCenter
861  removeObserver:_outlineViewDelegate
862  name:CPOutlineViewItemWillExpandNotification
863  object:self];
864 
865  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidExpand:)])
866  [defaultCenter
867  removeObserver:_outlineViewDelegate
868  name:CPOutlineViewItemDidExpandNotification
869  object:self];
870 
871  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillCollapse:)])
872  [defaultCenter
873  removeObserver:_outlineViewDelegate
874  name:CPOutlineViewItemWillCollapseNotification
875  object:self];
876 
877  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidCollapse:)])
878  [defaultCenter
879  removeObserver:_outlineViewDelegate
880  name:CPOutlineViewItemDidCollapseNotification
881  object:self];
882  }
883 
884  _outlineViewDelegate = aDelegate;
885  _implementedOutlineViewDelegateMethods = 0;
886 
887  var delegateMethods = [
888  CPOutlineViewDelegate_outlineView_dataViewForTableColumn_item_ , @selector(outlineView:dataViewForTableColumn:item:),
889  CPOutlineViewDelegate_outlineView_viewForTableColumn_item_ , @selector(outlineView:viewForTableColumn:item:),
890  CPOutlineViewDelegate_outlineView_didClickTableColumn_ , @selector(outlineView:didClickTableColumn:),
891  CPOutlineViewDelegate_outlineView_didDragTableColumn_ , @selector(outlineView:didDragTableColumn:),
892  CPOutlineViewDelegate_outlineView_heightOfRowByItem_ , @selector(outlineView:heightOfRowByItem:),
893  CPOutlineViewDelegate_outlineView_isGroupItem_ , @selector(outlineView:isGroupItem:),
894  CPOutlineViewDelegate_outlineView_mouseDownInHeaderOfTableColumn_ , @selector(outlineView:mouseDownInHeaderOfTableColumn:),
895  CPOutlineViewDelegate_outlineView_nextTypeSelectMatchFromItem_toItem_forString_ , @selector(outlineView:nextTypeSelectMatchFromItem:toItem:forString:),
896  CPOutlineViewDelegate_outlineView_selectionIndexesForProposedSelection_ , @selector(outlineView:selectionIndexesForProposedSelection:),
897  CPOutlineViewDelegate_outlineView_shouldCollapseItem_ , @selector(outlineView:shouldCollapseItem:),
898  CPOutlineViewDelegate_outlineView_shouldEditTableColumn_item_ , @selector(outlineView:shouldEditTableColumn:item:),
899  CPOutlineViewDelegate_outlineView_shouldExpandItem_ , @selector(outlineView:shouldExpandItem:),
900  CPOutlineViewDelegate_outlineView_shouldReorderColumn_toColumn_ , @selector(outlineView:shouldReorderColumn:toColumn:),
901  CPOutlineViewDelegate_outlineView_shouldSelectItem_ , @selector(outlineView:shouldSelectItem:),
902  CPOutlineViewDelegate_outlineView_shouldSelectTableColumn_ , @selector(outlineView:shouldSelectTableColumn:),
903  CPOutlineViewDelegate_outlineView_shouldShowOutlineDisclosureControlForItem_ , @selector(outlineView:shouldShowOutlineDisclosureControlForItem:),
904  CPOutlineViewDelegate_outlineView_shouldShowViewExpansionForTableColumn_item_ , @selector(outlineView:shouldShowViewExpansionForTableColumn:item:),
905  CPOutlineViewDelegate_outlineView_shouldTrackView_forTableColumn_item_ , @selector(outlineView:shouldTrackView:forTableColumn:item:),
906  CPOutlineViewDelegate_outlineView_shouldTypeSelectForEvent_withCurrentSearchString_ , @selector(outlineView:shouldTypeSelectForEvent:withCurrentSearchString:),
907  CPOutlineViewDelegate_outlineView_sizeToFitWidthOfColumn_ , @selector(outlineView:sizeToFitWidthOfColumn:),
908  CPOutlineViewDelegate_outlineView_toolTipForView_rect_tableColumn_item_mouseLocation_, @selector(outlineView:toolTipForView:rect:tableColumn:item:mouseLocation:),
909  CPOutlineViewDelegate_outlineView_typeSelectStringForTableColumn_item_ , @selector(outlineView:typeSelectStringForTableColumn:item:),
910  CPOutlineViewDelegate_outlineView_willDisplayOutlineView_forTableColumn_item_ , @selector(outlineView:willDisplayOutlineView:forTableColumn:item:),
911  CPOutlineViewDelegate_outlineView_willDisplayView_forTableColumn_item_ , @selector(outlineView:willDisplayView:forTableColumn:item:),
912  CPOutlineViewDelegate_selectionShouldChangeInOutlineView_ , @selector(selectionShouldChangeInOutlineView:),
913  CPOutlineViewDelegate_outlineView_menuForTableColumn_item_ , @selector(outlineView:menuForTableColumn:item:)
914  ],
915  delegateCount = [delegateMethods count];
916 
917  for (var i = 0; i < delegateCount; i += 2)
918  {
919  var bitMask = delegateMethods[i],
920  selector = delegateMethods[i + 1];
921 
922  if ([_outlineViewDelegate respondsToSelector:selector])
923  _implementedOutlineViewDelegateMethods |= bitMask;
924  }
925 
926  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidMove:)])
927  [defaultCenter
928  addObserver:_outlineViewDelegate
929  selector:@selector(outlineViewColumnDidMove:)
930  name:CPOutlineViewColumnDidMoveNotification
931  object:self];
932 
933  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidResize:)])
934  [defaultCenter
935  addObserver:_outlineViewDelegate
936  selector:@selector(outlineViewColumnDidMove:)
937  name:CPOutlineViewColumnDidResizeNotification
938  object:self];
939 
940  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionDidChange:)])
941  [defaultCenter
942  addObserver:_outlineViewDelegate
943  selector:@selector(outlineViewSelectionDidChange:)
944  name:CPOutlineViewSelectionDidChangeNotification
945  object:self];
946 
947  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionIsChanging:)])
948  [defaultCenter
949  addObserver:_outlineViewDelegate
950  selector:@selector(outlineViewSelectionIsChanging:)
951  name:CPOutlineViewSelectionIsChangingNotification
952  object:self];
953 
954  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillExpand:)])
955  [defaultCenter
956  addObserver:_outlineViewDelegate
957  selector:@selector(outlineViewItemWillExpand:)
958  name:CPOutlineViewItemWillExpandNotification
959  object:self];
960 
961  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidExpand:)])
962  [defaultCenter
963  addObserver:_outlineViewDelegate
964  selector:@selector(outlineViewItemDidExpand:)
965  name:CPOutlineViewItemDidExpandNotification
966  object:self];
967 
968  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillCollapse:)])
969  [defaultCenter
970  addObserver:_outlineViewDelegate
971  selector:@selector(outlineViewItemWillCollapse:)
972  name:CPOutlineViewItemWillCollapseNotification
973  object:self];
974 
975  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidCollapse:)])
976  [defaultCenter
977  addObserver:_outlineViewDelegate
978  selector:@selector(outlineViewItemDidCollapse:)
979  name:CPOutlineViewItemDidCollapseNotification
980  object:self];
981 
982  [self _updateIsViewBased];
983 
984  if ([self _delegateRespondsToDataViewForTableColumn])
985  CPLog.warn("outlineView:dataViewForTableColumn:item: is deprecated. You should use -outlineView:viewForTableColumn:item: where you can request the view with -makeViewWithIdentifier:owner:");
986 }
987 
988 - (BOOL)_sendDelegateDeleteKeyPressed
989 {
990  if ([[self delegate] respondsToSelector: @selector(outlineViewDeleteKeyPressed:)])
991  {
992  [[self delegate] outlineViewDeleteKeyPressed:self];
993  return YES;
994  }
995 
996  return NO;
997 }
998 
1002 - (id)delegate
1003 {
1004  return _outlineViewDelegate;
1005 }
1006 
1014 - (void)setDisclosureControlPrototype:(CPControl)aControl
1015 {
1016  _disclosureControlPrototype = aControl;
1017  _disclosureControlData = nil;
1018  _disclosureControlQueue = [];
1019 
1020  // FIXME: really?
1021  [self reloadData];
1022 }
1023 
1027 - (void)reloadData
1028 {
1029  [self reloadItem:nil reloadChildren:YES];
1030 }
1031 
1042 - (void)addTableColumn:(CPTableColumn)aTableColumn
1043 {
1044  [super addTableColumn:aTableColumn];
1045 
1046  if ([self numberOfColumns] === 1)
1047  _outlineTableColumn = aTableColumn;
1048 }
1052 - (void)removeTableColumn:(CPTableColumn)aTableColumn
1053 {
1054  if (aTableColumn === [self outlineTableColumn])
1055  CPLog("CPOutlineView cannot remove outlineTableColumn with removeTableColumn:. User setOutlineTableColumn: instead.");
1056  else
1057  [super removeTableColumn:aTableColumn];
1058 }
1064 - (CGRect)frameOfDataViewAtColumn:(CPInteger)aColumn row:(CPInteger)aRow
1065 {
1066  var tableColumn = [self tableColumns][aColumn];
1067 
1068  if (tableColumn === _outlineTableColumn)
1069  return [self _frameOfOutlineDataViewAtRow:aRow];
1070 
1071  return [super frameOfDataViewAtColumn:aColumn row:aRow];
1072 }
1073 
1078 - (CPView)_dragViewForColumn:(CPInteger)theColumnIndex event:(CPEvent)theDragEvent offset:(CGPoint)theDragViewOffset
1079 {
1080  var dragView = [[_CPColumnDragView alloc] initWithLineColor:[self gridColor]],
1081  tableColumn = [[self tableColumns] objectAtIndex:theColumnIndex],
1082  defaultRowHeight = [self valueForThemeAttribute:@"default-row-height"],
1083  bounds = CGRectMake(0.0, 0.0, [tableColumn width], CGRectGetHeight([self exposedRect]) + defaultRowHeight),
1084  columnRect = [self rectOfColumn:theColumnIndex],
1085  headerView = [tableColumn headerView],
1086  row = [_exposedRows firstIndex];
1087 
1088  while (row !== CPNotFound)
1089  {
1090  var dataView = [self _newDataViewForRow:row tableColumn:tableColumn],
1091  dataViewFrame = [self frameOfDataViewAtColumn:theColumnIndex row:row];
1092 
1093  // Only one column is ever dragged so we just place the view at
1094  dataViewFrame.origin.x = 0.0;
1095 
1096  // Offset by table header height - scroll position
1097  dataViewFrame.origin.y = (CGRectGetMinY(dataViewFrame) - CGRectGetMinY([self exposedRect])) + defaultRowHeight;
1098  [dataView setFrame:dataViewFrame];
1099 
1100  [dataView setObjectValue:[self _objectValueForTableColumn:tableColumn row:row]];
1101 
1102 
1103  if (tableColumn === _outlineTableColumn)
1104  {
1105  // first inset the dragview
1106  var indentationWidth = ([self levelForRow:row] + 1) * [self indentationPerLevel];
1107 
1108  dataViewFrame.origin.x += indentationWidth;
1109  dataViewFrame.size.width -= indentationWidth;
1110 
1111  [dataView setFrame:dataViewFrame];
1112  }
1113 
1114  [dragView addSubview:dataView];
1115 
1116  row = [_exposedRows indexGreaterThanIndex:row];
1117  }
1118 
1119  // Add the column header view
1120  var headerFrame = [headerView frame];
1121  headerFrame.origin = CGPointMakeZero();
1122 
1123  var columnHeaderView = [[_CPTableColumnHeaderView alloc] initWithFrame:headerFrame];
1124  [columnHeaderView setStringValue:[headerView stringValue]];
1125  [columnHeaderView setThemeState:[headerView themeState]];
1126  [dragView addSubview:columnHeaderView];
1127 
1128  [dragView setBackgroundColor:[CPColor whiteColor]];
1129  [dragView setAlphaValue:0.7];
1130  [dragView setFrame:bounds];
1131 
1132  return dragView;
1133 }
1134 
1150 - (void)setDropItem:(id)theItem dropChildIndex:(int)theIndex
1151 {
1152  if (_dropItem !== theItem && theIndex < 0 && [self isExpandable:theItem] && ![self isItemExpanded:theItem])
1153  {
1154  if (_dragHoverTimer)
1155  [_dragHoverTimer invalidate];
1156 
1157  var autoExpandCallBack = function()
1158  {
1159  if (_dropItem)
1160  {
1161  [_dropOperationFeedbackView blink];
1162  [CPTimer scheduledTimerWithTimeInterval:.3 callback:objj_msgSend(self, "expandItem:", _dropItem) repeats:NO];
1163  }
1164  };
1165 
1166  _dragHoverTimer = [CPTimer scheduledTimerWithTimeInterval:.8 callback:autoExpandCallBack repeats:NO];
1167  }
1168 
1169  if (theIndex >= 0)
1170  {
1171  [_dragHoverTimer invalidate];
1172  _dragHoverTimer = nil;
1173  }
1174 
1175  _dropItem = theItem;
1176  _retargetedItem = theItem;
1177  _shouldRetargetItem = YES;
1178 
1179  _retargedChildIndex = theIndex;
1180  _shouldRetargetChildIndex = YES;
1181 
1182  // set CPTableView's _retargetedDropRow based on retargetedItem and retargetedChildIndex
1183  var retargetedItemInfo = (_retargetedItem !== nil) ? _itemInfosForItems[[_retargetedItem UID]] : _rootItemInfo;
1184 
1185  if (_retargedChildIndex === [retargetedItemInfo.children count])
1186  {
1187  var retargetedChildItem = [retargetedItemInfo.children lastObject];
1188  _retargetedDropRow = [self rowForItem:retargetedChildItem] + 1;
1189  }
1190  else
1191  {
1192  var retargetedChildItem = (_retargedChildIndex !== CPOutlineViewDropOnItemIndex) ? retargetedItemInfo.children[_retargedChildIndex] : _retargetedItem;
1193  _retargetedDropRow = [self rowForItem:retargetedChildItem];
1194  }
1195 }
1196 
1200 - (void)_draggingEnded
1201 {
1202  [super _draggingEnded];
1203  _dropItem = nil;
1204  [_dragHoverTimer invalidate];
1205  _dragHoverTimer = nil;
1206 }
1207 
1211 - (id)_parentItemForUpperRow:(CPInteger)theUpperRowIndex andLowerRow:(CPInteger)theLowerRowIndex atMouseOffset:(CGPoint)theOffset
1212 {
1213  if (_shouldRetargetItem)
1214  return _retargetedItem;
1215 
1216  var lowerLevel = [self levelForRow:theLowerRowIndex],
1217  upperItem = [self itemAtRow:theUpperRowIndex],
1218  upperLevel = [self levelForItem:upperItem];
1219 
1220  // If the row above us has a higher level the item can be added to multiple parent items
1221  // Determine which one by looping through all possible parents and return the first
1222  // of which the indentation level is larger than the current x offset
1223  while (upperLevel > lowerLevel)
1224  {
1225  upperLevel = [self levelForItem:upperItem];
1226 
1227  // See if this item's indentation level matches the mouse offset
1228  if (theOffset.x > (upperLevel + 1) * [self indentationPerLevel])
1229  return [self parentForItem:upperItem];
1230 
1231  // Check the next parent
1232  upperItem = [self parentForItem:upperItem];
1233  }
1234 
1235  return [self parentForItem:[self itemAtRow:theLowerRowIndex]];
1236 }
1237 
1241 - (CGRect)_rectForDropHighlightViewBetweenUpperRow:(CPInteger)theUpperRowIndex andLowerRow:(CPInteger)theLowerRowIndex offset:(CGPoint)theOffset
1242 {
1243  // Call super and the update x to reflect the current indentation level
1244  var rect = [super _rectForDropHighlightViewBetweenUpperRow:theUpperRowIndex andLowerRow:theLowerRowIndex offset:theOffset],
1245  parentItem = [self _parentItemForUpperRow:theUpperRowIndex andLowerRow:theLowerRowIndex atMouseOffset:theOffset],
1246  level = [self levelForItem:parentItem];
1247 
1248  rect.origin.x = (level + 1) * [self indentationPerLevel];
1249  rect.size.width -= rect.origin.x; // This assumes that the x returned by super is zero
1250 
1251  return rect;
1252 }
1253 
1258 - (void)_layoutDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
1259 {
1260  var rowArray = [],
1261  columnArray = [];
1262 
1263  [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
1264  [columns getIndexes:columnArray maxCount:-1 inIndexRange:nil];
1265 
1266  var columnIndex = 0,
1267  columnsCount = columnArray.length;
1268 
1269  for (; columnIndex < columnsCount; ++columnIndex)
1270  {
1271  var column = columnArray[columnIndex],
1272  tableColumn = _tableColumns[column],
1273  tableColumnUID = [tableColumn UID],
1274  dataViewsForTableColumn = _dataViewsForTableColumns[tableColumnUID],
1275  rowIndex = 0,
1276  rowsCount = rowArray.length;
1277 
1278  for (; rowIndex < rowsCount; ++rowIndex)
1279  {
1280  var row = rowArray[rowIndex],
1281  dataView = dataViewsForTableColumn[row],
1282  dataViewFrame = [self frameOfDataViewAtColumn:column row:row];
1283 
1284  [dataView setFrame:dataViewFrame];
1285 
1286  if (tableColumn === _outlineTableColumn)
1287  {
1288  var control = _disclosureControlsForRows[row],
1289  frame = [self frameOfOutlineDisclosureControlAtRow:row];
1290 
1291  [control setFrame:frame];
1292  }
1293  }
1294  }
1295 }
1296 
1300 - (void)_loadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
1301 {
1302  [super _loadDataViewsInRows:rows columns:columns];
1303 
1304  var outlineColumn = [[self tableColumns] indexOfObjectIdenticalTo:[self outlineTableColumn]];
1305 
1306  if (![columns containsIndex:outlineColumn] || [self outlineTableColumn] === _draggedColumn)
1307  return;
1308 
1309  var rowArray = [];
1310 
1311  [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
1312 
1313  var rowIndex = 0,
1314  rowsCount = rowArray.length;
1315 
1316  for (; rowIndex < rowsCount; ++rowIndex)
1317  {
1318  var row = rowArray[rowIndex],
1319  item = _itemsForRows[row],
1320  isExpandable = [self isExpandable:item];
1321 
1322  if (!isExpandable)
1323  continue;
1324 
1325  var disclosureControlFrame = [self frameOfOutlineDisclosureControlAtRow:row];
1326 
1327  if (CGRectIsEmpty(disclosureControlFrame))
1328  continue;
1329 
1330  var control = [self _dequeueDisclosureControl];
1331 
1332  _disclosureControlsForRows[row] = control;
1333 
1334  [control setState:[self isItemExpanded:item] ? CPOnState : CPOffState];
1335  var selector = [self isRowSelected:row] ? @"setThemeState:" : @"unsetThemeState:";
1336  [control performSelector:CPSelectorFromString(selector) withObject:CPThemeStateSelected];
1337  [control setFrame:disclosureControlFrame];
1338 
1339  [self addSubview:control];
1340  }
1341 }
1342 
1346 - (void)_unloadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
1347 {
1348  [super _unloadDataViewsInRows:rows columns:columns];
1349 
1350  var outlineColumn = [[self tableColumns] indexOfObjectIdenticalTo:[self outlineTableColumn]];
1351 
1352  if (![columns containsIndex:outlineColumn])
1353  return;
1354 
1355  var rowArray = [];
1356 
1357  [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
1358 
1359  var rowIndex = 0,
1360  rowsCount = rowArray.length;
1361 
1362  for (; rowIndex < rowsCount; ++rowIndex)
1363  {
1364  var row = rowArray[rowIndex],
1365  control = _disclosureControlsForRows[row];
1366 
1367  if (!control)
1368  continue;
1369 
1370  [control removeFromSuperview];
1371 
1372  [self _enqueueDisclosureControl:control];
1373 
1374  _disclosureControlsForRows[row] = nil;
1375  }
1376 }
1377 
1381 - (void)_toggleFromDisclosureControl:(CPControl)aControl
1382 {
1383  var controlFrame = [aControl frame],
1384  item = [self itemAtRow:[self rowAtPoint:CGPointMake(CGRectGetMinX(controlFrame), CGRectGetMidY(controlFrame))]];
1385 
1386  if ([self isItemExpanded:item])
1387  [self collapseItem:item];
1388 
1389  else
1390  [self expandItem:item expandChildren:([[CPApp currentEvent] modifierFlags] & CPAlternateKeyMask)];
1391 }
1392 
1396 - (void)_enqueueDisclosureControl:(CPControl)aControl
1397 {
1398  _disclosureControlQueue.push(aControl);
1399 }
1400 
1404 - (CPControl)_dequeueDisclosureControl
1405 {
1406  if (_disclosureControlQueue.length)
1407  return _disclosureControlQueue.pop();
1408 
1409  if (!_disclosureControlData)
1410  if (!_disclosureControlPrototype)
1411  return nil;
1412  else
1413  _disclosureControlData = [CPKeyedArchiver archivedDataWithRootObject:_disclosureControlPrototype];
1414 
1415  var disclosureControl = [CPKeyedUnarchiver unarchiveObjectWithData:_disclosureControlData];
1416 
1417  [disclosureControl setTarget:self];
1418  [disclosureControl setAction:@selector(_toggleFromDisclosureControl:)];
1419 
1420  return disclosureControl;
1421 }
1422 
1426 - (void)_noteSelectionIsChanging
1427 {
1428  if (!_coalesceSelectionNotificationState || _coalesceSelectionNotificationState === CPOutlineViewCoalesceSelectionNotificationStateOn)
1429  {
1431  postNotificationName:CPOutlineViewSelectionIsChangingNotification
1432  object:self
1433  userInfo:nil];
1434  }
1435 
1436  if (_coalesceSelectionNotificationState === CPOutlineViewCoalesceSelectionNotificationStateOn)
1437  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateDid;
1438 }
1439 
1443 - (void)_noteSelectionDidChange
1444 {
1445  if (!_coalesceSelectionNotificationState)
1446  {
1448  postNotificationName:CPOutlineViewSelectionDidChangeNotification
1449  object:self
1450  userInfo:nil];
1451  }
1452 
1453  if (_coalesceSelectionNotificationState === CPOutlineViewCoalesceSelectionNotificationStateOn)
1454  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateDid;
1455 }
1456 
1460 - (void)_noteItemWillExpand:(id)item
1461 {
1463  postNotificationName:CPOutlineViewItemWillExpandNotification
1464  object:self
1465  userInfo:@{ "CPObject": item }];
1466 }
1467 
1471 - (void)_noteItemDidExpand:(id)item
1472 {
1474  postNotificationName:CPOutlineViewItemDidExpandNotification
1475  object:self
1476  userInfo:@{ "CPObject": item }];
1477 }
1478 
1482 - (void)_noteItemWillCollapse:(id)item
1483 {
1485  postNotificationName:CPOutlineViewItemWillCollapseNotification
1486  object:self
1487  userInfo:@{ "CPObject": item }];
1488 }
1489 
1493 - (void)_noteItemDidCollapse:(id)item
1494 {
1496  postNotificationName:CPOutlineViewItemDidCollapseNotification
1497  object:self
1498  userInfo:@{ "CPObject": item }];
1499 }
1500 
1501 - (void)keyDown:(CPEvent)anEvent
1502 {
1503  var character = [anEvent charactersIgnoringModifiers],
1504  modifierFlags = [anEvent modifierFlags];
1505 
1506  // Check for the key events manually, as opposed to waiting for CPWindow to sent the actual action message
1507  // in _processKeyboardUIKey:, because we might not want to handle the arrow events.
1508 
1509  if (character !== CPRightArrowFunctionKey && character !== CPLeftArrowFunctionKey)
1510  return [super keyDown:anEvent];
1511 
1512  var rows = [self selectedRowIndexes],
1513  indexes = [],
1514  items = [];
1515 
1516  [rows getIndexes:indexes maxCount:-1 inIndexRange:nil];
1517 
1518  var i = 0,
1519  c = [indexes count];
1520 
1521  for (; i < c; i++)
1522  items.push([self itemAtRow:indexes[i]]);
1523 
1524  if (character === CPRightArrowFunctionKey)
1525  {
1526  for (var i = 0; i < c; i++)
1527  [self expandItem:items[i]];
1528  }
1529  else if (character === CPLeftArrowFunctionKey)
1530  {
1531  // When a single, collapsed item is selected and the left arrow key is pressed, the parent
1532  // should be selected if possible.
1533  if (c == 1)
1534  {
1535  var theItem = items[0];
1536  if (![self isItemExpanded:theItem])
1537  {
1538  var parent = [self parentForItem:theItem],
1539  shouldSelect = parent && SELECTION_SHOULD_CHANGE(self) && SHOULD_SELECT_ITEM(self, parent);
1540  if (shouldSelect)
1541  {
1542  var rowIndex = [self rowForItem:parent];
1543  [self selectRowIndexes:[CPIndexSet indexSetWithIndex:rowIndex] byExtendingSelection:NO];
1544  [self scrollRowToVisible:rowIndex];
1545  return;
1546  }
1547  }
1548  }
1549 
1550  for (var i = 0; i < c; i++)
1551  [self collapseItem:items[i]];
1552  }
1553 
1554  [super keyDown:anEvent];
1555 }
1556 
1557 - (CPView)_sendDelegateViewForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
1558 {
1559  return [_outlineViewDelegate outlineView:self viewForTableColumn:aTableColumn item:[self itemAtRow:aRow]];
1560 }
1561 
1562 - (CPView)_sendDelegateDataViewForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
1563 {
1564  return [_outlineViewDelegate outlineView:self dataViewForTableColumn:aTableColumn item:[self itemAtRow:aRow]];
1565 }
1566 
1567 - (BOOL)_dataSourceRespondsToObjectValueForTableColumn
1568 {
1569  return _implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_objectValue_forTableColumn_byItem_;
1570 }
1571 
1572 - (BOOL)_delegateRespondsToViewForTableColumn
1573 {
1574  return _implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_viewForTableColumn_item_;
1575 }
1576 
1577 - (BOOL)_delegateRespondsToDataViewForTableColumn
1578 {
1579  return _implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_dataViewForTableColumn_item_;
1580 }
1581 
1582 - (BOOL)_delegateRespondsToShouldExpandItem
1583 {
1584  return _implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldExpandItem_;
1585 }
1586 
1587 - (BOOL)_delegateRespondsToShouldCollapseItem
1588 {
1589  return _implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldCollapseItem_;
1590 }
1591 
1596 - (BOOL)_delegateRespondsToSelectionIndexesForProposedSelection
1597 {
1598  return _implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_selectionIndexesForProposedSelection_;
1599 }
1600 
1605 - (BOOL)_delegateRespondsToShouldSelectRow
1606 {
1607  return _implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectItem_;
1608 }
1609 
1610 @end
1611 
1612 // FIX ME: We're using with() here because Safari fails if we use anOutlineView._itemInfosForItems or whatever...
1613 var _reloadItem = function(/*CPOutlineView*/ anOutlineView, /*id*/ anItem)
1614 {
1615  if (!anItem)
1616  return;
1617 
1618  with (anOutlineView)
1619  {
1620  // Get the existing info if it exists.
1621  var itemInfosForItems = _itemInfosForItems,
1622  dataSource = _outlineViewDataSource,
1623  itemUID = [anItem UID],
1624  itemInfo = itemInfosForItems[itemUID];
1625 
1626  // If we're not in the tree, then just bail.
1627  if (!itemInfo)
1628  return [];
1629 
1630  // See if the item itself can be swapped out.
1631  var parent = itemInfo.parent,
1632  parentItemInfo = parent ? itemInfosForItems[[parent UID]] : _rootItemInfo,
1633  parentChildren = parentItemInfo.children,
1634  index = [parentChildren indexOfObjectIdenticalTo:anItem],
1635  newItem = [dataSource outlineView:anOutlineView child:index ofItem:parent];
1636 
1637  if (anItem !== newItem)
1638  {
1639  itemInfosForItems[[anItem UID]] = nil;
1640  itemInfosForItems[[newItem UID]] = itemInfo;
1641 
1642  parentChildren[index] = newItem;
1643  _itemsForRows[itemInfo.row] = newItem;
1644  }
1645 
1646  itemInfo.isExpandable = [dataSource outlineView:anOutlineView isItemExpandable:newItem];
1647  itemInfo.isExpanded = itemInfo.isExpandable && itemInfo.isExpanded;
1648  itemInfo.shouldShowOutlineDisclosureControl = !(_implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldShowOutlineDisclosureControlForItem_) || [_outlineViewDelegate outlineView:self shouldShowOutlineDisclosureControlForItem:newItem];
1649  }
1650 };
1651 
1652 // FIX ME: We're using with() here because Safari fails if we use anOutlineView._itemInfosForItems or whatever...
1653 var _loadItemInfoForItem = function(/*CPOutlineView*/ anOutlineView, /*id*/ anItem, /*BOOL*/ isIntermediate)
1654 {
1655  with (anOutlineView)
1656  {
1657  var itemInfosForItems = _itemInfosForItems,
1658  dataSource = _outlineViewDataSource;
1659 
1660  if (!anItem)
1661  var itemInfo = _rootItemInfo;
1662 
1663  else
1664  {
1665  // Get the existing info if it exists.
1666  var itemUID = [anItem UID],
1667  itemInfo = itemInfosForItems[itemUID];
1668 
1669  // If we're not in the tree, then just bail.
1670  if (!itemInfo)
1671  return [];
1672 
1673  itemInfo.isExpandable = [dataSource outlineView:anOutlineView isItemExpandable:anItem];
1674  itemInfo.shouldShowOutlineDisclosureControl = !(_implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldShowOutlineDisclosureControlForItem_) || [_outlineViewDelegate outlineView:self shouldShowOutlineDisclosureControlForItem:anItem];
1675 
1676  // If we were previously expanded, but now no longer expandable, "de-expand".
1677  // NOTE: we are *not* collapsing, thus no notification is posted.
1678  if (!itemInfo.isExpandable && itemInfo.isExpanded)
1679  {
1680  itemInfo.isExpanded = NO;
1681  itemInfo.children = [];
1682  }
1683  }
1684 
1685  // The root item does not count as a descendant.
1686  var weight = itemInfo.weight,
1687  descendants = anItem ? [anItem] : [];
1688 
1689  if (itemInfo.isExpanded && (!(_implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_shouldDeferDisplayingChildrenOfItem_) ||
1690  ![dataSource outlineView:anOutlineView shouldDeferDisplayingChildrenOfItem:anItem]))
1691  {
1692  var index = 0,
1693  count = [dataSource outlineView:anOutlineView numberOfChildrenOfItem:anItem],
1694  level = itemInfo.level + 1;
1695 
1696  itemInfo.children = [];
1697 
1698  for (; index < count; ++index)
1699  {
1700  var childItem = [dataSource outlineView:anOutlineView child:index ofItem:anItem],
1701  childItemInfo = itemInfosForItems[[childItem UID]];
1702 
1703  if (!childItemInfo)
1704  {
1705  childItemInfo = { isExpanded:NO, isExpandable:NO, shouldShowOutlineDisclosureControl:YES, children:[], weight:1 };
1706  itemInfosForItems[[childItem UID]] = childItemInfo;
1707  }
1708 
1709  itemInfo.children[index] = childItem;
1710 
1711  var childDescendants = _loadItemInfoForItem(anOutlineView, childItem, YES);
1712 
1713  childItemInfo.parent = anItem;
1714  childItemInfo.level = level;
1715  descendants = descendants.concat(childDescendants);
1716  }
1717  }
1718 
1719  itemInfo.weight = descendants.length;
1720 
1721  if (!isIntermediate)
1722  {
1723  // row = -1 is the root item, so just go to row 0 since it is ignored.
1724  var index = MAX(itemInfo.row, 0),
1725  itemsForRows = _itemsForRows;
1726 
1727  descendants.unshift(index, weight);
1728 
1729  itemsForRows.splice.apply(itemsForRows, descendants);
1730 
1731  var count = itemsForRows.length;
1732 
1733  for (; index < count; ++index)
1734  itemInfosForItems[[itemsForRows[index] UID]].row = index;
1735 
1736  var deltaWeight = itemInfo.weight - weight;
1737 
1738  if (deltaWeight !== 0)
1739  {
1740  var parent = itemInfo.parent;
1741 
1742  while (parent)
1743  {
1744  var parentItemInfo = itemInfosForItems[[parent UID]];
1745 
1746  parentItemInfo.weight += deltaWeight;
1747  parent = parentItemInfo.parent;
1748  }
1749 
1750  if (anItem)
1751  _rootItemInfo.weight += deltaWeight;
1752  }
1753  }
1754  }//end of with
1755  return descendants;
1756 };
1757 
1758 @implementation _CPOutlineViewTableViewDataSource : CPObject
1759 {
1760  CPObject _outlineView;
1761 }
1762 
1763 - (id)initWithOutlineView:(CPOutlineView)anOutlineView
1764 {
1765  self = [super init];
1766 
1767  if (self)
1768  _outlineView = anOutlineView;
1769 
1770  return self;
1771 }
1772 
1773 - (CPInteger)numberOfRowsInTableView:(CPTableView)anOutlineView
1774 {
1775  return _outlineView._itemsForRows.length;
1776 }
1777 
1778 - (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
1779 {
1780  return [_outlineView._outlineViewDataSource outlineView:_outlineView objectValueForTableColumn:aTableColumn byItem:_outlineView._itemsForRows[aRow]];
1781 }
1782 
1783 - (void)tableView:(CPTableView)aTableView setObjectValue:(id)aValue forTableColumn:(CPTableColumn)aColumn row:(CPInteger)aRow
1784 {
1785  if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_setObjectValue_forTableColumn_byItem_))
1786  return;
1787  [_outlineView._outlineViewDataSource outlineView:_outlineView setObjectValue:aValue forTableColumn:aColumn byItem:_outlineView._itemsForRows[aRow]];
1788 }
1789 
1790 - (BOOL)tableView:(CPTableView)aTableColumn writeRowsWithIndexes:(CPIndexSet)theIndexes toPasteboard:(CPPasteboard)thePasteboard
1791 {
1792  if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_writeItems_toPasteboard_))
1793  return NO;
1794 
1795  var items = [],
1796  index = [theIndexes firstIndex];
1797 
1798  while (index !== CPNotFound)
1799  {
1800  [items addObject:[_outlineView itemAtRow:index]]
1801  index = [theIndexes indexGreaterThanIndex:index];
1802  }
1803 
1804  return [_outlineView._outlineViewDataSource outlineView:_outlineView writeItems:items toPasteboard:thePasteboard];
1805 }
1806 
1807 - (int)_childIndexForDropOperation:(CPTableViewDropOperation)theDropOperation row:(CPInteger)theRow offset:(CGPoint)theOffset
1808 {
1809  if (_outlineView._shouldRetargetChildIndex)
1810  return _outlineView._retargedChildIndex;
1811 
1812  var childIndex = CPNotFound;
1813 
1814  if (theDropOperation === CPTableViewDropAbove)
1815  {
1816  var parentItem = [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset],
1817  itemInfo = (parentItem !== nil) ? _outlineView._itemInfosForItems[[parentItem UID]] : _outlineView._rootItemInfo,
1818  children = itemInfo.children;
1819 
1820  childIndex = [children indexOfObject:[_outlineView itemAtRow:theRow]];
1821 
1822  if (childIndex === CPNotFound)
1823  childIndex = children.length;
1824  }
1825  else if (theDropOperation === CPTableViewDropOn)
1826  childIndex = -1;
1827 
1828  return childIndex;
1829 }
1830 
1831 - (void)_parentItemForDropOperation:(CPTableViewDropOperation)theDropOperation row:(CPInteger)theRow offset:(CGPoint)theOffset
1832 {
1833  if (theDropOperation === CPTableViewDropAbove)
1834  return [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset]
1835 
1836  return [_outlineView itemAtRow:theRow];
1837 }
1838 
1839 - (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(id /*< CPDraggingInfo >*/)theInfo
1840  proposedRow:(CPInteger)theRow proposedDropOperation:(CPTableViewDropOperation)theOperation
1841 {
1842  if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_validateDrop_proposedItem_proposedChildIndex_))
1843  return CPDragOperationNone;
1844 
1845  // Make sure the retargeted item and index are reset
1846  _outlineView._retargetedItem = nil;
1847  _outlineView._shouldRetargetItem = NO;
1848 
1849  _outlineView._retargedChildIndex = nil;
1850  _outlineView._shouldRetargetChildIndex = NO;
1851 
1852  var location = [_outlineView convertPoint:[theInfo draggingLocation] fromView:nil],
1853  parentItem = [self _parentItemForDropOperation:theOperation row:theRow offset:location],
1854  childIndex = [self _childIndexForDropOperation:theOperation row:theRow offset:location];
1855 
1856  return [_outlineView._outlineViewDataSource outlineView:_outlineView validateDrop:theInfo proposedItem:parentItem proposedChildIndex:childIndex];
1857 }
1858 
1859 - (BOOL)tableView:(CPTableView)aTableView acceptDrop:(id /*<CPDraggingInfo>*/)theInfo row:(CPInteger)theRow dropOperation:(CPTableViewDropOperation)theOperation
1860 {
1861  if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_acceptDrop_item_childIndex_))
1862  return NO;
1863 
1864  var location = [_outlineView convertPoint:[theInfo draggingLocation] fromView:nil],
1865  parentItem = [self _parentItemForDropOperation:theOperation row:theRow offset:location],
1866  childIndex = [self _childIndexForDropOperation:theOperation row:theRow offset:location];
1867 
1868  _outlineView._retargetedItem = nil;
1869  _outlineView._shouldRetargetItem = NO;
1870 
1871  _outlineView._retargedChildIndex = nil;
1872  _outlineView._shouldRetargetChildIndex = NO;
1873 
1874  return [_outlineView._outlineViewDataSource outlineView:_outlineView acceptDrop:theInfo item:parentItem childIndex:childIndex];
1875 }
1876 
1877 - (void)tableView:(CPTableView)aTableView sortDescriptorsDidChange:(CPArray)oldSortDescriptors
1878 {
1879  if ((_outlineView._implementedOutlineViewDataSourceMethods &
1881  {
1882  [[_outlineView dataSource] outlineView:_outlineView sortDescriptorsDidChange:oldSortDescriptors];
1883  }
1884 }
1885 
1886 @end
1887 
1888 @implementation _CPOutlineViewTableViewDelegate : CPObject
1889 {
1890  CPOutlineView _outlineView;
1891 }
1892 
1893 - (id)initWithOutlineView:(CPOutlineView)anOutlineView
1894 {
1895  self = [super init];
1896 
1897  if (self)
1898  _outlineView = anOutlineView;
1899 
1900  return self;
1901 }
1902 
1903 - (BOOL)tableView:(CPTableView)theTableView shouldSelectRow:(CPInteger)theRow
1904 {
1905  return SHOULD_SELECT_ITEM(_outlineView, [_outlineView itemAtRow:theRow]);
1906 }
1907 
1908 - (BOOL)selectionShouldChangeInTableView:(CPTableView)theTableView
1909 {
1910  return SELECTION_SHOULD_CHANGE(_outlineView);
1911 }
1912 
1913 - (BOOL)tableView:(CPTableView)aTableView shouldEditTableColumn:(CPTableColumn)aColumn row:(CPInteger)aRow
1914 {
1915  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldEditTableColumn_item_))
1916  return [_outlineView._outlineViewDelegate outlineView:_outlineView shouldEditTableColumn:aColumn item:[_outlineView itemAtRow:aRow]];
1917 
1918  return NO;
1919 }
1920 
1921 - (float)tableView:(CPTableView)theTableView heightOfRow:(CPInteger)theRow
1922 {
1923  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_heightOfRowByItem_))
1924  return [_outlineView._outlineViewDelegate outlineView:_outlineView heightOfRowByItem:[_outlineView itemAtRow:theRow]];
1925 
1926  return [theTableView rowHeight];
1927 }
1928 
1929 - (void)tableView:(CPTableView)aTableView willDisplayView:(id)aView forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
1930 {
1931  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_willDisplayView_forTableColumn_item_))
1932  {
1933  var item = [_outlineView itemAtRow:aRowIndex];
1934  [_outlineView._outlineViewDelegate outlineView:_outlineView willDisplayView:aView forTableColumn:aTableColumn item:item];
1935  }
1936 }
1937 
1938 - (BOOL)tableView:(CPTableView)aTableView isGroupRow:(CPInteger)aRow
1939 {
1940  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_isGroupItem_))
1941  return [_outlineView._outlineViewDelegate outlineView:_outlineView isGroupItem:[_outlineView itemAtRow:aRow]];
1942 
1943  return NO;
1944 }
1945 
1946 - (CPMenu)tableView:(CPTableView)aTableView menuForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
1947 {
1948  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_menuForTableColumn_item_))
1949  {
1950  var item = [_outlineView itemAtRow:aRow];
1951  return [_outlineView._outlineViewDelegate outlineView:_outlineView menuForTableColumn:aTableColumn item:item]
1952  }
1953 
1954  // We reimplement CPView menuForEvent: because we can't call it directly. CPTableView implements menuForEvent:
1955  // to call this delegate method.
1956  return [_outlineView menu] || [[_outlineView class] defaultMenu];
1957 }
1958 
1959 - (CPIndexSet)tableView:(CPTableView)aTableView selectionIndexesForProposedSelection:(CPIndexSet)anIndexSet
1960 {
1961  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_selectionIndexesForProposedSelection_))
1962  return [_outlineView._outlineViewDelegate outlineView:_outlineView selectionIndexesForProposedSelection:anIndexSet];
1963 
1964  return anIndexSet;
1965 }
1966 
1967 - (BOOL)tableView:(CPTableView)aTableView shouldSelectTableColumn:(CPTableColumn)aTableColumn
1968 {
1969  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectTableColumn_))
1970  return [_outlineView._outlineViewDelegate outlineView:_outlineView shouldSelectTableColumn:aTableColumn];
1971 
1972  return YES;
1973 }
1974 
1975 @end
1976 
1977 @implementation CPDisclosureButton : CPButton
1978 {
1979  float _angle;
1980 }
1981 
1982 - (id)initWithFrame:(CGRect)aFrame
1983 {
1984  self = [super initWithFrame:aFrame];
1985 
1986  if (self)
1987  [self setBordered:NO];
1988 
1989  return self;
1990 }
1991 
1992 - (void)setState:(CPInteger)aState
1993 {
1994  [super setState:aState];
1995 
1996  if ([self state] === CPOnState)
1997  _angle = 0.0;
1998 
1999  else
2000  _angle = -PI_2;
2001 }
2002 
2003 - (void)drawRect:(CGRect)aRect
2004 {
2005  var bounds = [self bounds],
2007  width = CGRectGetWidth(bounds),
2008  height = CGRectGetHeight(bounds);
2009 
2010  CGContextBeginPath(context);
2011 
2012  if (_angle)
2013  {
2014  var centre = CGPointMake(FLOOR(width / 2.0), FLOOR(height / 2.0));
2015  CGContextTranslateCTM(context, centre.x, centre.y);
2016  CGContextRotateCTM(context, _angle);
2017  CGContextTranslateCTM(context, -centre.x, -centre.y);
2018  }
2019 
2020  // Center, but crisp.
2021  CGContextTranslateCTM(context, FLOOR((width - 9.0) / 2.0), FLOOR((height - 8.0) / 2.0));
2022 
2023  CGContextMoveToPoint(context, 0.0, 0.0);
2024  CGContextAddLineToPoint(context, 9.0, 0.0);
2025  CGContextAddLineToPoint(context, 4.5, 8.0);
2026  CGContextClosePath(context);
2027 
2028  CGContextSetFillColor(context,
2029  colorForDisclosureTriangle([self hasThemeState:CPThemeStateSelected],
2030  [self hasThemeState:CPThemeStateHighlighted]));
2031  CGContextFillPath(context);
2032 
2033  CGContextBeginPath(context);
2034  CGContextMoveToPoint(context, 0.0, 0.0);
2035  CGContextAddLineToPoint(context, 4.5, 8.0);
2036 
2037  if (_angle === 0.0)
2038  CGContextAddLineToPoint(context, 9.0, 0.0);
2039 
2040  CGContextSetStrokeColor(context, [CPColor colorWithCalibratedWhite:1.0 alpha: 0.7]);
2041  CGContextStrokePath(context);
2042 }
2043 
2044 @end
2045 
2046 
2047 var CPOutlineViewIndentationPerLevelKey = @"CPOutlineViewIndentationPerLevelKey",
2048  CPOutlineViewOutlineTableColumnKey = @"CPOutlineViewOutlineTableColumnKey",
2049  CPOutlineViewDataSourceKey = @"CPOutlineViewDataSourceKey",
2050  CPOutlineViewDelegateKey = @"CPOutlineViewDelegateKey";
2051 
2053 
2054 - (id)initWithCoder:(CPCoder)aCoder
2055 {
2056  self = [super initWithCoder:aCoder];
2057 
2058  if (self)
2059  {
2060  // The root item has weight "0", thus represents the weight solely of its descendants.
2061  _rootItemInfo = { isExpanded:YES, isExpandable:NO, level:-1, row:-1, children:[], weight:0 };
2062 
2063  _itemsForRows = [];
2064  _itemInfosForItems = { };
2065  _disclosureControlsForRows = [];
2066 
2068  [self setDisclosureControlPrototype:[[CPDisclosureButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 10.0)]];
2069 
2070  _outlineTableColumn = [aCoder decodeObjectForKey:CPOutlineViewOutlineTableColumnKey];
2071  _indentationPerLevel = [aCoder decodeFloatForKey:CPOutlineViewIndentationPerLevelKey];
2072 
2073  _outlineViewDataSource = [aCoder decodeObjectForKey:CPOutlineViewDataSourceKey];
2074  _outlineViewDelegate = [aCoder decodeObjectForKey:CPOutlineViewDelegateKey];
2075 
2076  [super setDataSource:[[_CPOutlineViewTableViewDataSource alloc] initWithOutlineView:self]];
2077  [super setDelegate:[[_CPOutlineViewTableViewDelegate alloc] initWithOutlineView:self]];
2078 
2079  [self _updateIsViewBased];
2080  }
2081 
2082  return self;
2083 }
2084 
2085 - (void)encodeWithCoder:(CPCoder)aCoder
2086 {
2087  // Make sure we don't encode our internal delegate and data source.
2088  var internalDelegate = _delegate,
2089  internalDataSource = _dataSource;
2090  _delegate = nil;
2091  _dataSource = nil;
2092  [super encodeWithCoder:aCoder];
2093  _delegate = internalDelegate;
2094  _dataSource = internalDataSource;
2095 
2096  [aCoder encodeObject:_outlineTableColumn forKey:CPOutlineViewOutlineTableColumnKey];
2097  [aCoder encodeFloat:_indentationPerLevel forKey:CPOutlineViewIndentationPerLevelKey];
2098 
2099  [aCoder encodeObject:_outlineViewDataSource forKey:CPOutlineViewDataSourceKey];
2100  [aCoder encodeObject:_outlineViewDelegate forKey:CPOutlineViewDelegateKey];
2101 }
2102 
2103 @end
2104 
2105 
2106 var colorForDisclosureTriangle = function(isSelected, isHighlighted)
2107 {
2108  return isSelected
2109  ? (isHighlighted
2111  : [CPColor colorWithCalibratedWhite:1.0 alpha: 1.0])
2112  : (isHighlighted
2113  ? [CPColor colorWithCalibratedWhite:0.4 alpha: 1.0]
2114  : [CPColor colorWithCalibratedWhite:0.5 alpha: 1.0]);
2115 };