API  0.9.6
 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 
25 CPOutlineViewColumnDidMoveNotification = @"CPOutlineViewColumnDidMoveNotification";
26 CPOutlineViewColumnDidResizeNotification = @"CPOutlineViewColumnDidResizeNotification";
27 CPOutlineViewItemDidCollapseNotification = @"CPOutlineViewItemDidCollapseNotification";
28 CPOutlineViewItemDidExpandNotification = @"CPOutlineViewItemDidExpandNotification";
29 CPOutlineViewItemWillCollapseNotification = @"CPOutlineViewItemWillCollapseNotification";
30 CPOutlineViewItemWillExpandNotification = @"CPOutlineViewItemWillExpandNotification";
31 CPOutlineViewSelectionDidChangeNotification = @"CPOutlineViewSelectionDidChangeNotification";
32 CPOutlineViewSelectionIsChangingNotification = @"CPOutlineViewSelectionIsChangingNotification";
33 
36 
41 
44 
46 
48 
74 
76 
80 
81 #define SELECTION_SHOULD_CHANGE(anOutlineView) (!((anOutlineView)._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_selectionShouldChangeInOutlineView_) || [(anOutlineView)._outlineViewDelegate selectionShouldChangeInOutlineView:(anOutlineView)])
82 
83 #define SHOULD_SELECT_ITEM(anOutlineView, anItem) (!((anOutlineView)._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectItem_) || [(anOutlineView)._outlineViewDelegate outlineView:(anOutlineView) shouldSelectItem:(anItem)])
84 
100 @implementation CPOutlineView : CPTableView
101 {
102  id _outlineViewDataSource;
103  id _outlineViewDelegate;
104  CPTableColumn _outlineTableColumn;
105 
106  float _indentationPerLevel;
107  BOOL _indentationMarkerFollowsDataView;
108 
109  CPInteger _implementedOutlineViewDataSourceMethods;
110  CPInteger _implementedOutlineViewDelegateMethods;
111 
112  Object _rootItemInfo;
113  CPMutableArray _itemsForRows;
114  Object _itemInfosForItems;
115 
116  CPControl _disclosureControlPrototype;
117  CPArray _disclosureControlsForRows;
118  CPData _disclosureControlData;
119  CPArray _disclosureControlQueue;
120 
121  BOOL _shouldRetargetItem;
122  id _retargetedItem;
123 
124  BOOL _shouldRetargetChildIndex;
125  CPInteger _retargedChildIndex;
126  CPTimer _dragHoverTimer;
127  id _dropItem;
128 
129  BOOL _coalesceSelectionNotificationState;
130 }
131 
132 - (id)initWithFrame:(CGRect)aFrame
133 {
134  self = [super initWithFrame:aFrame];
135 
136  if (self)
137  {
138  _selectionHighlightStyle = CPTableViewSelectionHighlightStyleSourceList;
139 
140  // The root item has weight "0", thus represents the weight solely of its descendants.
141  _rootItemInfo = { isExpanded:YES, isExpandable:NO, shouldShowOutlineDisclosureControl:NO, level:-1, row:-1, children:[], weight:0 };
142 
143  _itemsForRows = [];
144  _itemInfosForItems = { };
145  _disclosureControlsForRows = [];
146 
147  _retargetedItem = nil;
148  _shouldRetargetItem = NO;
149 
150  _retargedChildIndex = nil;
151  _shouldRetargetChildIndex = NO;
152 
153  [self setIndentationPerLevel:16.0];
155 
156  [super setDataSource:[[_CPOutlineViewTableViewDataSource alloc] initWithOutlineView:self]];
157  [super setDelegate:[[_CPOutlineViewTableViewDelegate alloc] initWithOutlineView:self]];
158 
159  [self setDisclosureControlPrototype:[[CPDisclosureButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 10.0)]];
160  }
161 
162  return self;
163 }
215 - (void)setDataSource:(id)aDataSource
216 {
217  if (_outlineViewDataSource === aDataSource)
218  return;
219 
220  if (![aDataSource respondsToSelector:@selector(outlineView:child:ofItem:)])
221  [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:child:ofItem:'"];
222 
223  if (![aDataSource respondsToSelector:@selector(outlineView:isItemExpandable:)])
224  [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:isItemExpandable:'"];
225 
226  if (![aDataSource respondsToSelector:@selector(outlineView:numberOfChildrenOfItem:)])
227  [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:numberOfChildrenOfItem:'"];
228 
229  if (![aDataSource respondsToSelector:@selector(outlineView:objectValueForTableColumn:byItem:)])
230  [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:objectValueForTableColumn:byItem:'"];
231 
232  _outlineViewDataSource = aDataSource;
233  _implementedOutlineViewDataSourceMethods = 0;
234 
235  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:setObjectValue:forTableColumn:byItem:)])
236  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_setObjectValue_forTableColumn_byItem_;
237 
238  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:shouldDeferDisplayingChildrenOfItem:)])
239  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_shouldDeferDisplayingChildrenOfItem_;
240 
241  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:acceptDrop:item:childIndex:)])
242  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_acceptDrop_item_childIndex_;
243 
244  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:validateDrop:proposedItem:proposedChildIndex:)])
246 
247  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:validateDrop:proposedRow:proposedDropOperation:)])
249 
250  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:namesOfPromisedFilesDroppedAtDestination:forDraggedItems:)])
252 
253  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:itemForPersistentObject:)])
254  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_itemForPersistentObject_;
255 
256  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:persistentObjectForItem:)])
257  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_persistentObjectForItem_;
258 
259  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:writeItems:toPasteboard:)])
260  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_writeItems_toPasteboard_;
261 
262  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:sortDescriptorsDidChange:)])
263  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_sortDescriptorsDidChange_;
264 
265  [self reloadData];
266 }
267 
273 - (id)dataSource
274 {
275  return _outlineViewDataSource;
276 }
277 
285 - (BOOL)isExpandable:(id)anItem
286 {
287  if (!anItem)
288  return YES;
289 
290  var itemInfo = _itemInfosForItems[[anItem UID]];
291 
292  if (!itemInfo)
293  return NO;
294 
295  return itemInfo.isExpandable;
296 }
297 
298 - (BOOL)_shouldShowOutlineDisclosureControlForItem:(id)anItem
299 {
300  if (!anItem)
301  return YES;
302 
303  var itemInfo = _itemInfosForItems[[anItem UID]];
304 
305  if (!itemInfo)
306  return YES;
307 
308  return itemInfo.shouldShowOutlineDisclosureControl;
309 }
310 
318 - (BOOL)isItemExpanded:(id)anItem
319 {
320  if (!anItem)
321  return YES;
322 
323  var itemInfo = _itemInfosForItems[[anItem UID]];
324 
325  if (!itemInfo)
326  return NO;
327 
328  return itemInfo.isExpanded;
329 }
330 
336 - (void)expandItem:(id)anItem
337 {
338  [self expandItem:anItem expandChildren:NO];
339 }
340 
347 - (void)expandItem:(id)anItem expandChildren:(BOOL)shouldExpandChildren
348 {
349  var itemInfo = null;
350 
351  if (!anItem)
352  itemInfo = _rootItemInfo;
353  else
354  itemInfo = _itemInfosForItems[[anItem UID]];
355 
356  if (!itemInfo)
357  return;
358 
359  // When shouldExpandChildren is YES, we need to make sure we're collecting
360  // selection notifications so that exactly one IsChanging and one
361  // DidChange is sent as needed, for the totality of the operation.
362  var isTopLevel = NO;
363  if (!_coalesceSelectionNotificationState)
364  {
365  isTopLevel = YES;
366  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOn;
367  }
368 
369  // To prevent items which are already expanded from firing notifications.
370  if (!itemInfo.isExpanded)
371  {
372  [self _noteItemWillExpand:anItem];
373 
374  var previousRowCount = [self numberOfRows];
375 
376  itemInfo.isExpanded = YES;
377  [self reloadItem:anItem reloadChildren:YES];
378  [self _noteItemDidExpand:anItem];
379 
380  // Shift selection indexes below so that the same items remain selected.
381  var rowCountDelta = [self numberOfRows] - previousRowCount;
382  if (rowCountDelta)
383  {
384  var selection = [self selectedRowIndexes],
385  expandIndex = [self rowForItem:anItem] + 1;
386 
387  if ([selection intersectsIndexesInRange:CPMakeRange(expandIndex, _itemsForRows.length)])
388  {
389  [self _noteSelectionIsChanging];
390  [selection shiftIndexesStartingAtIndex:expandIndex by:rowCountDelta];
391  [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
392  }
393  }
394  }
395 
396  if (shouldExpandChildren)
397  {
398  var children = itemInfo.children,
399  childIndex = children.length;
400 
401  while (childIndex--)
402  [self expandItem:children[childIndex] expandChildren:YES];
403  }
404 
405  if (isTopLevel)
406  {
407  var r = _coalesceSelectionNotificationState;
408  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOff;
410  [self _noteSelectionDidChange];
411  }
412 }
413 
419 - (void)collapseItem:(id)anItem
420 {
421  if (!anItem)
422  return;
423 
424  var itemInfo = _itemInfosForItems[[anItem UID]];
425 
426  if (!itemInfo)
427  return;
428 
429  if (!itemInfo.isExpanded)
430  return;
431 
432  // Don't spam notifications.
433  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOn;
434 
435  [self _noteItemWillCollapse:anItem];
436  // Update selections:
437  // * Deselect items inside the collapsed item.
438  // * Shift row selections below the collapsed item so that the same logical items remain selected.
439  var collapseTopIndex = [self rowForItem:anItem],
440  topLevel = [self levelForRow:collapseTopIndex],
441  collapseEndIndex = collapseTopIndex;
442 
443  while (collapseEndIndex + 1 < _itemsForRows.length && [self levelForRow:collapseEndIndex + 1] > topLevel)
444  collapseEndIndex++;
445 
446  var collapseRange = CPMakeRange(collapseTopIndex + 1, collapseEndIndex - collapseTopIndex);
447 
448  if (collapseRange.length)
449  {
450  var selection = [self selectedRowIndexes];
451 
452  if ([selection intersectsIndexesInRange:collapseRange])
453  {
454  [self _noteSelectionIsChanging];
455  [selection removeIndexesInRange:collapseRange];
456  [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
457  }
458 
459  // Shift any selected rows below upwards.
460  if ([selection intersectsIndexesInRange:CPMakeRange(collapseEndIndex + 1, _itemsForRows.length)])
461  {
462  [self _noteSelectionIsChanging];
463  [selection shiftIndexesStartingAtIndex:collapseEndIndex + 1 by:-collapseRange.length];
464  [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
465  }
466  }
467  itemInfo.isExpanded = NO;
468 
469  [self reloadItem:anItem reloadChildren:YES];
470  [self _noteItemDidCollapse:anItem];
471 
472  // Send selection notifications only after the items have loaded so that
473  // the new selection is consistent with the actual rows for any observers.
474  var r = _coalesceSelectionNotificationState;
475  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOff;
477  [self _noteSelectionDidChange];
478 }
479 
485 - (void)reloadItem:(id)anItem
486 {
487  [self reloadItem:anItem reloadChildren:NO];
488 }
489 
496 - (void)reloadItem:(id)anItem reloadChildren:(BOOL)shouldReloadChildren
497 {
498  if (!!shouldReloadChildren || !anItem)
499  _loadItemInfoForItem(self, anItem);
500  else
501  _reloadItem(self, anItem);
502 
503  [super reloadData];
504 }
505 
512 - (id)itemAtRow:(CPInteger)aRow
513 {
514  return _itemsForRows[aRow] || nil;
515 }
516 
523 - (CPInteger)rowForItem:(id)anItem
524 {
525  if (!anItem)
526  return _rootItemInfo.row;
527 
528  var itemInfo = _itemInfosForItems[[anItem UID]];
529 
530  if (!itemInfo)
531  return CPNotFound;
532 
533  return itemInfo.row;
534 }
535 
542 - (void)setOutlineTableColumn:(CPTableColumn)aTableColumn
543 {
544  if (_outlineTableColumn === aTableColumn)
545  return;
546 
547  _outlineTableColumn = aTableColumn;
548 
549  // FIXME: efficiency.
550  [self reloadData];
551 }
552 
558 - (CPTableColumn)outlineTableColumn
559 {
560  return _outlineTableColumn;
561 }
562 
571 - (CPInteger)levelForItem:(id)anItem
572 {
573  if (!anItem)
574  return _rootItemInfo.level;
575 
576  var itemInfo = _itemInfosForItems[[anItem UID]];
577 
578  if (!itemInfo)
579  return CPNotFound;
580 
581  return itemInfo.level;
582 }
583 
591 - (CPInteger)levelForRow:(CPInteger)aRow
592 {
593  return [self levelForItem:[self itemAtRow:aRow]];
594 }
595 
601 - (void)setIndentationPerLevel:(float)anIndentationWidth
602 {
603  if (_indentationPerLevel === anIndentationWidth)
604  return;
605 
606  _indentationPerLevel = anIndentationWidth;
607 
608  // FIXME: efficiency!!!!
609  [self reloadData];
610 }
611 
617 - (float)indentationPerLevel
618 {
619  return _indentationPerLevel;
620 }
621 
629 - (void)setIndentationMarkerFollowsDataView:(BOOL)indentationMarkerShouldFollowDataView
630 {
631  if (_indentationMarkerFollowsDataView === indentationMarkerShouldFollowDataView)
632  return;
633 
634  _indentationMarkerFollowsDataView = indentationMarkerShouldFollowDataView;
635 
636  // !!!!
637  [self reloadData];
638 }
639 
647 - (BOOL)indentationMarkerFollowsDataView
648 {
649  return _indentationMarkerFollowsDataView;
650 }
651 
659 - (id)parentForItem:(id)anItem
660 {
661  if (!anItem)
662  return nil;
663 
664  var itemInfo = _itemInfosForItems[[anItem UID]];
665 
666  if (!itemInfo)
667  return nil;
668 
669  var parent = itemInfo.parent;
670 
671  // Check if the parent is the root item because we never return the actual root item
672  if (itemInfo[[parent UID]] === _rootItemInfo)
673  parent = nil;
674 
675  return parent;
676 }
677 
683 - (CGRect)_frameOfOutlineDataViewAtRow:(CPInteger)aRow
684 {
685  var columnIndex = [[self tableColumns] indexOfObject:_outlineTableColumn],
686  frame = [super frameOfDataViewAtColumn:columnIndex row:aRow],
687  indentationWidth = ([self levelForRow:aRow] + 1) * [self indentationPerLevel];
688 
689  frame.origin.x += indentationWidth;
690  frame.size.width -= indentationWidth;
691 
692  return frame;
693 }
694 
703 - (CGRect)frameOfOutlineDisclosureControlAtRow:(CPInteger)aRow
704 {
705  var theItem = [self itemAtRow:aRow];
706  if (![self isExpandable:theItem] || ![self _shouldShowOutlineDisclosureControlForItem:theItem])
707  return _CGRectMakeZero();
708 
709  var dataViewFrame = [self _frameOfOutlineDataViewAtRow:aRow],
710  disclosureWidth = _CGRectGetWidth([_disclosureControlPrototype frame]),
711  frame = _CGRectMake(_CGRectGetMinX(dataViewFrame) - disclosureWidth, _CGRectGetMinY(dataViewFrame), disclosureWidth, _CGRectGetHeight(dataViewFrame));
712 
713  return frame;
714 }
715 
720 - (void)_performSelection:(BOOL)select forRow:(CPInteger)rowIndex context:(id)context
721 {
722  [super _performSelection:select forRow:rowIndex context:context];
723 
724  var control = _disclosureControlsForRows[rowIndex],
725  selector = select ? @"setThemeState:" : @"unsetThemeState:";
726 
727  [control performSelector:CPSelectorFromString(selector) withObject:CPThemeStateSelected];
728 }
729 
806 - (void)setDelegate:(id)aDelegate
807 {
808  if (_outlineViewDelegate === aDelegate)
809  return;
810 
811  var defaultCenter = [CPNotificationCenter defaultCenter];
812 
813  if (_outlineViewDelegate)
814  {
815  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidMove:)])
816  [defaultCenter
817  removeObserver:_outlineViewDelegate
818  name:CPOutlineViewColumnDidMoveNotification
819  object:self];
820 
821  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidResize:)])
822  [defaultCenter
823  removeObserver:_outlineViewDelegate
824  name:CPOutlineViewColumnDidResizeNotification
825  object:self];
826 
827  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionDidChange:)])
828  [defaultCenter
829  removeObserver:_outlineViewDelegate
830  name:CPOutlineViewSelectionDidChangeNotification
831  object:self];
832 
833  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionIsChanging:)])
834  [defaultCenter
835  removeObserver:_outlineViewDelegate
836  name:CPOutlineViewSelectionIsChangingNotification
837  object:self];
838 
839  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillExpand:)])
840  [defaultCenter
841  removeObserver:_outlineViewDelegate
842  name:CPOutlineViewItemWillExpandNotification
843  object:self];
844 
845  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidExpand:)])
846  [defaultCenter
847  removeObserver:_outlineViewDelegate
848  name:CPOutlineViewItemDidExpandNotification
849  object:self];
850 
851  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillCollapse:)])
852  [defaultCenter
853  removeObserver:_outlineViewDelegate
854  name:CPOutlineViewItemWillCollapseNotification
855  object:self];
856 
857  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidCollapse:)])
858  [defaultCenter
859  removeObserver:_outlineViewDelegate
860  name:CPOutlineViewItemDidCollapseNotification
861  object:self];
862  }
863 
864  _outlineViewDelegate = aDelegate;
865  _implementedOutlineViewDelegateMethods = 0;
866 
867  var delegateMethods = [
868  CPOutlineViewDelegate_outlineView_dataViewForTableColumn_item_ , @selector(outlineView:dataViewForTableColumn:item:),
869  CPOutlineViewDelegate_outlineView_didClickTableColumn_ , @selector(outlineView:didClickTableColumn:),
870  CPOutlineViewDelegate_outlineView_didDragTableColumn_ , @selector(outlineView:didDragTableColumn:),
871  CPOutlineViewDelegate_outlineView_heightOfRowByItem_ , @selector(outlineView:heightOfRowByItem:),
872  CPOutlineViewDelegate_outlineView_isGroupItem_ , @selector(outlineView:isGroupItem:),
873  CPOutlineViewDelegate_outlineView_mouseDownInHeaderOfTableColumn_ , @selector(outlineView:mouseDownInHeaderOfTableColumn:),
874  CPOutlineViewDelegate_outlineView_nextTypeSelectMatchFromItem_toItem_forString_ , @selector(outlineView:nextTypeSelectMatchFromItem:toItem:forString:),
875  CPOutlineViewDelegate_outlineView_selectionIndexesForProposedSelection_ , @selector(outlineView:selectionIndexesForProposedSelection:),
876  CPOutlineViewDelegate_outlineView_shouldCollapseItem_ , @selector(outlineView:shouldCollapseItem:),
877  CPOutlineViewDelegate_outlineView_shouldEditTableColumn_item_ , @selector(outlineView:shouldEditTableColumn:item:),
878  CPOutlineViewDelegate_outlineView_shouldExpandItem_ , @selector(outlineView:shouldExpandItem:),
879  CPOutlineViewDelegate_outlineView_shouldReorderColumn_toColumn_ , @selector(outlineView:shouldReorderColumn:toColumn:),
880  CPOutlineViewDelegate_outlineView_shouldSelectItem_ , @selector(outlineView:shouldSelectItem:),
881  CPOutlineViewDelegate_outlineView_shouldSelectTableColumn_ , @selector(outlineView:shouldSelectTableColumn:),
882  CPOutlineViewDelegate_outlineView_shouldShowOutlineDisclosureControlForItem_ , @selector(outlineView:shouldShowOutlineDisclosureControlForItem:),
883  CPOutlineViewDelegate_outlineView_shouldShowViewExpansionForTableColumn_item_ , @selector(outlineView:shouldShowViewExpansionForTableColumn:item:),
884  CPOutlineViewDelegate_outlineView_shouldTrackView_forTableColumn_item_ , @selector(outlineView:shouldTrackView:forTableColumn:item:),
885  CPOutlineViewDelegate_outlineView_shouldTypeSelectForEvent_withCurrentSearchString_ , @selector(outlineView:shouldTypeSelectForEvent:withCurrentSearchString:),
886  CPOutlineViewDelegate_outlineView_sizeToFitWidthOfColumn_ , @selector(outlineView:sizeToFitWidthOfColumn:),
887  CPOutlineViewDelegate_outlineView_toolTipForView_rect_tableColumn_item_mouseLocation_, @selector(outlineView:toolTipForView:rect:tableColumn:item:mouseLocation:),
888  CPOutlineViewDelegate_outlineView_typeSelectStringForTableColumn_item_ , @selector(outlineView:typeSelectStringForTableColumn:item:),
889  CPOutlineViewDelegate_outlineView_willDisplayOutlineView_forTableColumn_item_ , @selector(outlineView:willDisplayOutlineView:forTableColumn:item:),
890  CPOutlineViewDelegate_outlineView_willDisplayView_forTableColumn_item_ , @selector(outlineView:willDisplayView:forTableColumn:item:),
891  CPOutlineViewDelegate_selectionShouldChangeInOutlineView_ , @selector(selectionShouldChangeInOutlineView:),
892  CPOutlineViewDelegate_outlineView_menuForTableColumn_item_ , @selector(outlineView:menuForTableColumn:item:)
893  ],
894  delegateCount = [delegateMethods count];
895 
896  for (var i = 0; i < delegateCount; i += 2)
897  {
898  var bitMask = delegateMethods[i],
899  selector = delegateMethods[i + 1];
900 
901  if ([_outlineViewDelegate respondsToSelector:selector])
902  _implementedOutlineViewDelegateMethods |= bitMask;
903  }
904 
905  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidMove:)])
906  [defaultCenter
907  addObserver:_outlineViewDelegate
908  selector:@selector(outlineViewColumnDidMove:)
909  name:CPOutlineViewColumnDidMoveNotification
910  object:self];
911 
912  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidResize:)])
913  [defaultCenter
914  addObserver:_outlineViewDelegate
915  selector:@selector(outlineViewColumnDidMove:)
916  name:CPOutlineViewColumnDidResizeNotification
917  object:self];
918 
919  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionDidChange:)])
920  [defaultCenter
921  addObserver:_outlineViewDelegate
922  selector:@selector(outlineViewSelectionDidChange:)
923  name:CPOutlineViewSelectionDidChangeNotification
924  object:self];
925 
926  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionIsChanging:)])
927  [defaultCenter
928  addObserver:_outlineViewDelegate
929  selector:@selector(outlineViewSelectionIsChanging:)
930  name:CPOutlineViewSelectionIsChangingNotification
931  object:self];
932 
933  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillExpand:)])
934  [defaultCenter
935  addObserver:_outlineViewDelegate
936  selector:@selector(outlineViewItemWillExpand:)
937  name:CPOutlineViewItemWillExpandNotification
938  object:self];
939 
940  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidExpand:)])
941  [defaultCenter
942  addObserver:_outlineViewDelegate
943  selector:@selector(outlineViewItemDidExpand:)
944  name:CPOutlineViewItemDidExpandNotification
945  object:self];
946 
947  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillCollapse:)])
948  [defaultCenter
949  addObserver:_outlineViewDelegate
950  selector:@selector(outlineViewItemWillCollapse:)
951  name:CPOutlineViewItemWillCollapseNotification
952  object:self];
953 
954  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidCollapse:)])
955  [defaultCenter
956  addObserver:_outlineViewDelegate
957  selector:@selector(outlineViewItemDidCollapse:)
958  name:CPOutlineViewItemDidCollapseNotification
959  object:self];
960 
961 }
962 
963 - (BOOL)_sendDelegateDeleteKeyPressed
964 {
965  if ([[self delegate] respondsToSelector: @selector(outlineViewDeleteKeyPressed:)])
966  {
967  [[self delegate] outlineViewDeleteKeyPressed:self];
968  return YES;
969  }
970 
971  return NO;
972 }
973 
977 - (id)delegate
978 {
979  return _outlineViewDelegate;
980 }
981 
989 - (void)setDisclosureControlPrototype:(CPControl)aControl
990 {
991  _disclosureControlPrototype = aControl;
992  _disclosureControlData = nil;
993  _disclosureControlQueue = [];
994 
995  // FIXME: really?
996  [self reloadData];
997 }
998 
1002 - (void)reloadData
1003 {
1004  [self reloadItem:nil reloadChildren:YES];
1005 }
1006 
1017 - (void)addTableColumn:(CPTableColumn)aTableColumn
1018 {
1019  [super addTableColumn:aTableColumn];
1020 
1021  if ([self numberOfColumns] === 1)
1022  _outlineTableColumn = aTableColumn;
1023 }
1027 - (void)removeTableColumn:(CPTableColumn)aTableColumn
1028 {
1029  if (aTableColumn === [self outlineTableColumn])
1030  CPLog("CPOutlineView cannot remove outlineTableColumn with removeTableColumn:. User setOutlineTableColumn: instead.");
1031  else
1032  [super removeTableColumn:aTableColumn];
1033 }
1039 - (CGRect)frameOfDataViewAtColumn:(CPInteger)aColumn row:(CPInteger)aRow
1040 {
1041  var tableColumn = [self tableColumns][aColumn];
1042 
1043  if (tableColumn === _outlineTableColumn)
1044  return [self _frameOfOutlineDataViewAtRow:aRow];
1045 
1046  return [super frameOfDataViewAtColumn:aColumn row:aRow];
1047 }
1048 
1053 - (CPView)_dragViewForColumn:(int)theColumnIndex event:(CPEvent)theDragEvent offset:(CPPointPointer)theDragViewOffset
1054 {
1055  var dragView = [[_CPColumnDragView alloc] initWithLineColor:[self gridColor]],
1056  tableColumn = [[self tableColumns] objectAtIndex:theColumnIndex],
1057  bounds = _CGRectMake(0.0, 0.0, [tableColumn width], _CGRectGetHeight([self exposedRect]) + 23.0),
1058  columnRect = [self rectOfColumn:theColumnIndex],
1059  headerView = [tableColumn headerView],
1060  row = [_exposedRows firstIndex];
1061 
1062  while (row !== CPNotFound)
1063  {
1064  var dataView = [self _newDataViewForRow:row tableColumn:tableColumn],
1065  dataViewFrame = [self frameOfDataViewAtColumn:theColumnIndex row:row];
1066 
1067  // Only one column is ever dragged so we just place the view at
1068  dataViewFrame.origin.x = 0.0;
1069 
1070  // Offset by table header height - scroll position
1071  dataViewFrame.origin.y = ( _CGRectGetMinY(dataViewFrame) - _CGRectGetMinY([self exposedRect]) ) + 23.0;
1072  [dataView setFrame:dataViewFrame];
1073 
1074  [dataView setObjectValue:[self _objectValueForTableColumn:tableColumn row:row]];
1075 
1076 
1077  if (tableColumn === _outlineTableColumn)
1078  {
1079  // first inset the dragview
1080  var indentationWidth = ([self levelForRow:row] + 1) * [self indentationPerLevel];
1081 
1082  dataViewFrame.origin.x += indentationWidth;
1083  dataViewFrame.size.width -= indentationWidth;
1084 
1085  [dataView setFrame:dataViewFrame];
1086  }
1087 
1088  [dragView addSubview:dataView];
1089 
1090  row = [_exposedRows indexGreaterThanIndex:row];
1091  }
1092 
1093  // Add the column header view
1094  var headerFrame = [headerView frame];
1095  headerFrame.origin = _CGPointMakeZero();
1096 
1097  var columnHeaderView = [[_CPTableColumnHeaderView alloc] initWithFrame:headerFrame];
1098  [columnHeaderView setStringValue:[headerView stringValue]];
1099  [columnHeaderView setThemeState:[headerView themeState]];
1100  [dragView addSubview:columnHeaderView];
1101 
1102  [dragView setBackgroundColor:[CPColor whiteColor]];
1103  [dragView setAlphaValue:0.7];
1104  [dragView setFrame:bounds];
1105 
1106  return dragView;
1107 }
1108 
1124 - (void)setDropItem:(id)theItem dropChildIndex:(int)theIndex
1125 {
1126  if (_dropItem !== theItem && theIndex < 0 && [self isExpandable:theItem] && ![self isItemExpanded:theItem])
1127  {
1128  if (_dragHoverTimer)
1129  [_dragHoverTimer invalidate];
1130 
1131  var autoExpandCallBack = function()
1132  {
1133  if (_dropItem)
1134  {
1135  [_dropOperationFeedbackView blink];
1136  [CPTimer scheduledTimerWithTimeInterval:.3 callback:objj_msgSend(self, "expandItem:", _dropItem) repeats:NO];
1137  }
1138  };
1139 
1140  _dragHoverTimer = [CPTimer scheduledTimerWithTimeInterval:.8 callback:autoExpandCallBack repeats:NO];
1141  }
1142 
1143  if (theIndex >= 0)
1144  {
1145  [_dragHoverTimer invalidate];
1146  _dragHoverTimer = nil;
1147  }
1148 
1149  _dropItem = theItem;
1150  _retargetedItem = theItem;
1151  _shouldRetargetItem = YES;
1152 
1153  _retargedChildIndex = theIndex;
1154  _shouldRetargetChildIndex = YES;
1155 
1156  // set CPTableView's _retargetedDropRow based on retargetedItem and retargetedChildIndex
1157  var retargetedItemInfo = (_retargetedItem !== nil) ? _itemInfosForItems[[_retargetedItem UID]] : _rootItemInfo;
1158 
1159  if (_retargedChildIndex === [retargetedItemInfo.children count])
1160  {
1161  var retargetedChildItem = [retargetedItemInfo.children lastObject];
1162  _retargetedDropRow = [self rowForItem:retargetedChildItem] + 1;
1163  }
1164  else
1165  {
1166  var retargetedChildItem = (_retargedChildIndex !== CPOutlineViewDropOnItemIndex) ? retargetedItemInfo.children[_retargedChildIndex] : _retargetedItem;
1167  _retargetedDropRow = [self rowForItem:retargetedChildItem];
1168  }
1169 }
1170 
1174 - (void)_draggingEnded
1175 {
1176  [super _draggingEnded];
1177  _dropItem = nil;
1178  [_dragHoverTimer invalidate];
1179  _dragHoverTimer = nil;
1180 }
1181 
1185 - (id)_parentItemForUpperRow:(int)theUpperRowIndex andLowerRow:(int)theLowerRowIndex atMouseOffset:(CPPoint)theOffset
1186 {
1187  if (_shouldRetargetItem)
1188  return _retargetedItem;
1189 
1190  var lowerLevel = [self levelForRow:theLowerRowIndex],
1191  upperItem = [self itemAtRow:theUpperRowIndex],
1192  upperLevel = [self levelForItem:upperItem];
1193 
1194  // If the row above us has a higher level the item can be added to multiple parent items
1195  // Determine which one by looping through all possible parents and return the first
1196  // of which the indentation level is larger than the current x offset
1197  while (upperLevel > lowerLevel)
1198  {
1199  upperLevel = [self levelForItem:upperItem];
1200 
1201  // See if this item's indentation level matches the mouse offset
1202  if (theOffset.x > (upperLevel + 1) * [self indentationPerLevel])
1203  return [self parentForItem:upperItem];
1204 
1205  // Check the next parent
1206  upperItem = [self parentForItem:upperItem];
1207  }
1208 
1209  return [self parentForItem:[self itemAtRow:theLowerRowIndex]];
1210 }
1211 
1215 - (CPRect)_rectForDropHighlightViewBetweenUpperRow:(int)theUpperRowIndex andLowerRow:(int)theLowerRowIndex offset:(CPPoint)theOffset
1216 {
1217  // Call super and the update x to reflect the current indentation level
1218  var rect = [super _rectForDropHighlightViewBetweenUpperRow:theUpperRowIndex andLowerRow:theLowerRowIndex offset:theOffset],
1219  parentItem = [self _parentItemForUpperRow:theUpperRowIndex andLowerRow:theLowerRowIndex atMouseOffset:theOffset],
1220  level = [self levelForItem:parentItem];
1221 
1222  rect.origin.x = (level + 1) * [self indentationPerLevel];
1223  rect.size.width -= rect.origin.x; // This assumes that the x returned by super is zero
1224 
1225  return rect;
1226 }
1227 
1232 - (void)_layoutDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
1233 {
1234  var rowArray = [],
1235  columnArray = [];
1236 
1237  [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
1238  [columns getIndexes:columnArray maxCount:-1 inIndexRange:nil];
1239 
1240  var columnIndex = 0,
1241  columnsCount = columnArray.length;
1242 
1243  for (; columnIndex < columnsCount; ++columnIndex)
1244  {
1245  var column = columnArray[columnIndex],
1246  tableColumn = _tableColumns[column],
1247  tableColumnUID = [tableColumn UID],
1248  dataViewsForTableColumn = _dataViewsForTableColumns[tableColumnUID],
1249  rowIndex = 0,
1250  rowsCount = rowArray.length;
1251 
1252  for (; rowIndex < rowsCount; ++rowIndex)
1253  {
1254  var row = rowArray[rowIndex],
1255  dataView = dataViewsForTableColumn[row],
1256  dataViewFrame = [self frameOfDataViewAtColumn:column row:row];
1257 
1258  [dataView setFrame:dataViewFrame];
1259 
1260  if (tableColumn === _outlineTableColumn)
1261  {
1262  var control = _disclosureControlsForRows[row],
1263  frame = [self frameOfOutlineDisclosureControlAtRow:row];
1264 
1265  [control setFrame:frame];
1266  }
1267  }
1268  }
1269 }
1270 
1274 - (void)_loadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
1275 {
1276  [super _loadDataViewsInRows:rows columns:columns];
1277 
1278  var outlineColumn = [[self tableColumns] indexOfObjectIdenticalTo:[self outlineTableColumn]];
1279 
1280  if (![columns containsIndex:outlineColumn] || [self outlineTableColumn] === _draggedColumn)
1281  return;
1282 
1283  var rowArray = [];
1284 
1285  [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
1286 
1287  var rowIndex = 0,
1288  rowsCount = rowArray.length;
1289 
1290  for (; rowIndex < rowsCount; ++rowIndex)
1291  {
1292  var row = rowArray[rowIndex],
1293  item = _itemsForRows[row],
1294  isExpandable = [self isExpandable:item];
1295 
1296  if (!isExpandable)
1297  continue;
1298 
1299  var disclosureControlFrame = [self frameOfOutlineDisclosureControlAtRow:row];
1300 
1301  if (_CGRectIsEmpty(disclosureControlFrame))
1302  continue;
1303 
1304  var control = [self _dequeueDisclosureControl];
1305 
1306  _disclosureControlsForRows[row] = control;
1307 
1308  [control setState:[self isItemExpanded:item] ? CPOnState : CPOffState];
1309  var selector = [self isRowSelected:row] ? @"setThemeState:" : @"unsetThemeState:";
1310  [control performSelector:CPSelectorFromString(selector) withObject:CPThemeStateSelected];
1311  [control setFrame:disclosureControlFrame];
1312 
1313  [self addSubview:control];
1314  }
1315 }
1316 
1320 - (void)_unloadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
1321 {
1322  [super _unloadDataViewsInRows:rows columns:columns];
1323 
1324  var outlineColumn = [[self tableColumns] indexOfObjectIdenticalTo:[self outlineTableColumn]];
1325 
1326  if (![columns containsIndex:outlineColumn])
1327  return;
1328 
1329  var rowArray = [];
1330 
1331  [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
1332 
1333  var rowIndex = 0,
1334  rowsCount = rowArray.length;
1335 
1336  for (; rowIndex < rowsCount; ++rowIndex)
1337  {
1338  var row = rowArray[rowIndex],
1339  control = _disclosureControlsForRows[row];
1340 
1341  if (!control)
1342  continue;
1343 
1344  [control removeFromSuperview];
1345 
1346  [self _enqueueDisclosureControl:control];
1347 
1348  _disclosureControlsForRows[row] = nil;
1349  }
1350 }
1351 
1355 - (void)_toggleFromDisclosureControl:(CPControl)aControl
1356 {
1357  var controlFrame = [aControl frame],
1358  item = [self itemAtRow:[self rowAtPoint:_CGPointMake(_CGRectGetMinX(controlFrame), _CGRectGetMidY(controlFrame))]];
1359 
1360  if ([self isItemExpanded:item])
1361  [self collapseItem:item];
1362 
1363  else
1364  [self expandItem:item expandChildren:([[CPApp currentEvent] modifierFlags] & CPAlternateKeyMask)];
1365 }
1366 
1370 - (void)_enqueueDisclosureControl:(CPControl)aControl
1371 {
1372  _disclosureControlQueue.push(aControl);
1373 }
1374 
1378 - (CPControl)_dequeueDisclosureControl
1379 {
1380  if (_disclosureControlQueue.length)
1381  return _disclosureControlQueue.pop();
1382 
1383  if (!_disclosureControlData)
1384  if (!_disclosureControlPrototype)
1385  return nil;
1386  else
1387  _disclosureControlData = [CPKeyedArchiver archivedDataWithRootObject:_disclosureControlPrototype];
1388 
1389  var disclosureControl = [CPKeyedUnarchiver unarchiveObjectWithData:_disclosureControlData];
1390 
1391  [disclosureControl setTarget:self];
1392  [disclosureControl setAction:@selector(_toggleFromDisclosureControl:)];
1393 
1394  return disclosureControl;
1395 }
1396 
1400 - (void)_noteSelectionIsChanging
1401 {
1402  if (!_coalesceSelectionNotificationState || _coalesceSelectionNotificationState === CPOutlineViewCoalesceSelectionNotificationStateOn)
1403  {
1405  postNotificationName:CPOutlineViewSelectionIsChangingNotification
1406  object:self
1407  userInfo:nil];
1408  }
1409 
1410  if (_coalesceSelectionNotificationState === CPOutlineViewCoalesceSelectionNotificationStateOn)
1411  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateDid;
1412 }
1413 
1417 - (void)_noteSelectionDidChange
1418 {
1419  if (!_coalesceSelectionNotificationState)
1420  {
1422  postNotificationName:CPOutlineViewSelectionDidChangeNotification
1423  object:self
1424  userInfo:nil];
1425  }
1426 
1427  if (_coalesceSelectionNotificationState === CPOutlineViewCoalesceSelectionNotificationStateOn)
1428  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateDid;
1429 }
1430 
1434 - (void)_noteItemWillExpand:(id)item
1435 {
1437  postNotificationName:CPOutlineViewItemWillExpandNotification
1438  object:self
1439  userInfo:[CPDictionary dictionaryWithObject:item forKey:"CPObject"]];
1440 }
1441 
1445 - (void)_noteItemDidExpand:(id)item
1446 {
1448  postNotificationName:CPOutlineViewItemDidExpandNotification
1449  object:self
1450  userInfo:[CPDictionary dictionaryWithObject:item forKey:"CPObject"]];
1451 }
1452 
1456 - (void)_noteItemWillCollapse:(id)item
1457 {
1459  postNotificationName:CPOutlineViewItemWillCollapseNotification
1460  object:self
1461  userInfo:[CPDictionary dictionaryWithObject:item forKey:"CPObject"]];
1462 }
1463 
1467 - (void)_noteItemDidCollapse:(id)item
1468 {
1470  postNotificationName:CPOutlineViewItemDidCollapseNotification
1471  object:self
1472  userInfo:[CPDictionary dictionaryWithObject:item forKey:"CPObject"]];
1473 }
1474 
1475 - (void)keyDown:(CPEvent)anEvent
1476 {
1477  var character = [anEvent charactersIgnoringModifiers],
1478  modifierFlags = [anEvent modifierFlags];
1479 
1480  // Check for the key events manually, as opposed to waiting for CPWindow to sent the actual action message
1481  // in _processKeyboardUIKey:, because we might not want to handle the arrow events.
1482 
1483  if (character !== CPRightArrowFunctionKey && character !== CPLeftArrowFunctionKey)
1484  return [super keyDown:anEvent];
1485 
1486  var rows = [self selectedRowIndexes],
1487  indexes = [],
1488  items = [];
1489 
1490  [rows getIndexes:indexes maxCount:-1 inIndexRange:nil];
1491 
1492  var i = 0,
1493  c = [indexes count];
1494 
1495  for (; i < c; i++)
1496  items.push([self itemAtRow:indexes[i]]);
1497 
1498  if (character === CPRightArrowFunctionKey)
1499  {
1500  for (var i = 0; i < c; i++)
1501  [self expandItem:items[i]];
1502  }
1503  else if (character === CPLeftArrowFunctionKey)
1504  {
1505  // When a single, collapsed item is selected and the left arrow key is pressed, the parent
1506  // should be selected if possible.
1507  if (c == 1)
1508  {
1509  var theItem = items[0];
1510  if (![self isItemExpanded:theItem])
1511  {
1512  var parent = [self parentForItem:theItem],
1513  shouldSelect = parent && SELECTION_SHOULD_CHANGE(self) && SHOULD_SELECT_ITEM(self, parent);
1514  if (shouldSelect)
1515  {
1516  var rowIndex = [self rowForItem:parent];
1518  [self scrollRowToVisible:rowIndex];
1519  return;
1520  }
1521  }
1522  }
1523 
1524  for (var i = 0; i < c; i++)
1525  [self collapseItem:items[i]];
1526  }
1527 
1528  [super keyDown:anEvent];
1529 }
1530 
1531 @end
1532 
1533 // FIX ME: We're using with() here because Safari fails if we use anOutlineView._itemInfosForItems or whatever...
1534 var _reloadItem = function(/*CPOutlineView*/ anOutlineView, /*id*/ anItem)
1535 {
1536  if (!anItem)
1537  return;
1538 
1539  with (anOutlineView)
1540  {
1541  // Get the existing info if it exists.
1542  var itemInfosForItems = _itemInfosForItems,
1543  dataSource = _outlineViewDataSource,
1544  itemUID = [anItem UID],
1545  itemInfo = itemInfosForItems[itemUID];
1546 
1547  // If we're not in the tree, then just bail.
1548  if (!itemInfo)
1549  return [];
1550 
1551  // See if the item itself can be swapped out.
1552  var parent = itemInfo.parent,
1553  parentItemInfo = parent ? itemInfosForItems[[parent UID]] : _rootItemInfo,
1554  parentChildren = parentItemInfo.children,
1555  index = [parentChildren indexOfObjectIdenticalTo:anItem],
1556  newItem = [dataSource outlineView:anOutlineView child:index ofItem:parent];
1557 
1558  if (anItem !== newItem)
1559  {
1560  itemInfosForItems[[anItem UID]] = nil;
1561  itemInfosForItems[[newItem UID]] = itemInfo;
1562 
1563  parentChildren[index] = newItem;
1564  _itemsForRows[itemInfo.row] = newItem;
1565  }
1566 
1567  itemInfo.isExpandable = [dataSource outlineView:anOutlineView isItemExpandable:newItem];
1568  itemInfo.isExpanded = itemInfo.isExpandable && itemInfo.isExpanded;
1569  itemInfo.shouldShowOutlineDisclosureControl = !(_implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldShowOutlineDisclosureControlForItem_) || [_outlineViewDelegate outlineView:self shouldShowOutlineDisclosureControlForItem:newItem];
1570  }
1571 };
1572 
1573 // FIX ME: We're using with() here because Safari fails if we use anOutlineView._itemInfosForItems or whatever...
1574 var _loadItemInfoForItem = function(/*CPOutlineView*/ anOutlineView, /*id*/ anItem, /*BOOL*/ isIntermediate)
1575 {
1576  with (anOutlineView)
1577  {
1578  var itemInfosForItems = _itemInfosForItems,
1579  dataSource = _outlineViewDataSource;
1580 
1581  if (!anItem)
1582  var itemInfo = _rootItemInfo;
1583 
1584  else
1585  {
1586  // Get the existing info if it exists.
1587  var itemUID = [anItem UID],
1588  itemInfo = itemInfosForItems[itemUID];
1589 
1590  // If we're not in the tree, then just bail.
1591  if (!itemInfo)
1592  return [];
1593 
1594  itemInfo.isExpandable = [dataSource outlineView:anOutlineView isItemExpandable:anItem];
1595  itemInfo.shouldShowOutlineDisclosureControl = !(_implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldShowOutlineDisclosureControlForItem_) || [_outlineViewDelegate outlineView:self shouldShowOutlineDisclosureControlForItem:anItem];
1596 
1597  // If we were previously expanded, but now no longer expandable, "de-expand".
1598  // NOTE: we are *not* collapsing, thus no notification is posted.
1599  if (!itemInfo.isExpandable && itemInfo.isExpanded)
1600  {
1601  itemInfo.isExpanded = NO;
1602  itemInfo.children = [];
1603  }
1604  }
1605 
1606  // The root item does not count as a descendant.
1607  var weight = itemInfo.weight,
1608  descendants = anItem ? [anItem] : [];
1609 
1610  if (itemInfo.isExpanded && (!(_implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_shouldDeferDisplayingChildrenOfItem_) ||
1611  ![dataSource outlineView:anOutlineView shouldDeferDisplayingChildrenOfItem:anItem]))
1612  {
1613  var index = 0,
1614  count = [dataSource outlineView:anOutlineView numberOfChildrenOfItem:anItem],
1615  level = itemInfo.level + 1;
1616 
1617  itemInfo.children = [];
1618 
1619  for (; index < count; ++index)
1620  {
1621  var childItem = [dataSource outlineView:anOutlineView child:index ofItem:anItem],
1622  childItemInfo = itemInfosForItems[[childItem UID]];
1623 
1624  if (!childItemInfo)
1625  {
1626  childItemInfo = { isExpanded:NO, isExpandable:NO, shouldShowOutlineDisclosureControl:YES, children:[], weight:1 };
1627  itemInfosForItems[[childItem UID]] = childItemInfo;
1628  }
1629 
1630  itemInfo.children[index] = childItem;
1631 
1632  var childDescendants = _loadItemInfoForItem(anOutlineView, childItem, YES);
1633 
1634  childItemInfo.parent = anItem;
1635  childItemInfo.level = level;
1636  descendants = descendants.concat(childDescendants);
1637  }
1638  }
1639 
1640  itemInfo.weight = descendants.length;
1641 
1642  if (!isIntermediate)
1643  {
1644  // row = -1 is the root item, so just go to row 0 since it is ignored.
1645  var index = MAX(itemInfo.row, 0),
1646  itemsForRows = _itemsForRows;
1647 
1648  descendants.unshift(index, weight);
1649 
1650  itemsForRows.splice.apply(itemsForRows, descendants);
1651 
1652  var count = itemsForRows.length;
1653 
1654  for (; index < count; ++index)
1655  itemInfosForItems[[itemsForRows[index] UID]].row = index;
1656 
1657  var deltaWeight = itemInfo.weight - weight;
1658 
1659  if (deltaWeight !== 0)
1660  {
1661  var parent = itemInfo.parent;
1662 
1663  while (parent)
1664  {
1665  var parentItemInfo = itemInfosForItems[[parent UID]];
1666 
1667  parentItemInfo.weight += deltaWeight;
1668  parent = parentItemInfo.parent;
1669  }
1670 
1671  if (anItem)
1672  _rootItemInfo.weight += deltaWeight;
1673  }
1674  }
1675  }//end of with
1676  return descendants;
1677 };
1678 
1679 @implementation _CPOutlineViewTableViewDataSource : CPObject
1680 {
1681  CPObject _outlineView;
1682 }
1683 
1684 - (id)initWithOutlineView:(CPOutlineView)anOutlineView
1685 {
1686  self = [super init];
1687 
1688  if (self)
1689  _outlineView = anOutlineView;
1690 
1691  return self;
1692 }
1693 
1694 - (CPInteger)numberOfRowsInTableView:(CPTableView)anOutlineView
1695 {
1696  return _outlineView._itemsForRows.length;
1697 }
1698 
1699 - (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
1700 {
1701  return [_outlineView._outlineViewDataSource outlineView:_outlineView objectValueForTableColumn:aTableColumn byItem:_outlineView._itemsForRows[aRow]];
1702 }
1703 
1704 - (void)tableView:(CPTableView)aTableView setObjectValue:(id)aValue forTableColumn:(CPTableColumn)aColumn row:(CPInteger)aRow
1705 {
1706  if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_setObjectValue_forTableColumn_byItem_))
1707  return;
1708  [_outlineView._outlineViewDataSource outlineView:_outlineView setObjectValue:aValue forTableColumn:aColumn byItem:_outlineView._itemsForRows[aRow]];
1709 }
1710 
1711 - (BOOL)tableView:(CPTableView)aTableColumn writeRowsWithIndexes:(CPIndexSet)theIndexes toPasteboard:(CPPasteboard)thePasteboard
1712 {
1713  if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_writeItems_toPasteboard_))
1714  return NO;
1715 
1716  var items = [],
1717  index = [theIndexes firstIndex];
1718 
1719  while (index !== CPNotFound)
1720  {
1721  [items addObject:[_outlineView itemAtRow:index]]
1722  index = [theIndexes indexGreaterThanIndex:index];
1723  }
1724 
1725  return [_outlineView._outlineViewDataSource outlineView:_outlineView writeItems:items toPasteboard:thePasteboard];
1726 }
1727 
1728 - (int)_childIndexForDropOperation:(CPTableViewDropOperation)theDropOperation row:(int)theRow offset:(CPPoint)theOffset
1729 {
1730  if (_outlineView._shouldRetargetChildIndex)
1731  return _outlineView._retargedChildIndex;
1732 
1733  var childIndex = CPNotFound;
1734 
1735  if (theDropOperation === CPTableViewDropAbove)
1736  {
1737  var parentItem = [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset],
1738  itemInfo = (parentItem !== nil) ? _outlineView._itemInfosForItems[[parentItem UID]] : _outlineView._rootItemInfo,
1739  children = itemInfo.children;
1740 
1741  childIndex = [children indexOfObject:[_outlineView itemAtRow:theRow]];
1742 
1743  if (childIndex === CPNotFound)
1744  childIndex = children.length;
1745  }
1746  else if (theDropOperation === CPTableViewDropOn)
1747  childIndex = -1;
1748 
1749  return childIndex;
1750 }
1751 
1752 - (void)_parentItemForDropOperation:(CPTableViewDropOperation)theDropOperation row:(int)theRow offset:(CPPoint)theOffset
1753 {
1754  if (theDropOperation === CPTableViewDropAbove)
1755  return [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset]
1756 
1757  return [_outlineView itemAtRow:theRow];
1758 }
1759 
1760 - (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(id < CPDraggingInfo >)theInfo
1761  proposedRow:(int)theRow proposedDropOperation:(CPTableViewDropOperation)theOperation
1762 {
1763  if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_validateDrop_proposedItem_proposedChildIndex_))
1764  return CPDragOperationNone;
1765 
1766  // Make sure the retargeted item and index are reset
1767  _outlineView._retargetedItem = nil;
1768  _outlineView._shouldRetargetItem = NO;
1769 
1770  _outlineView._retargedChildIndex = nil;
1771  _outlineView._shouldRetargetChildIndex = NO;
1772 
1773  var location = [_outlineView convertPoint:[theInfo draggingLocation] fromView:nil],
1774  parentItem = [self _parentItemForDropOperation:theOperation row:theRow offset:location],
1775  childIndex = [self _childIndexForDropOperation:theOperation row:theRow offset:location];
1776 
1777  return [_outlineView._outlineViewDataSource outlineView:_outlineView validateDrop:theInfo proposedItem:parentItem proposedChildIndex:childIndex];
1778 }
1779 
1780 - (BOOL)tableView:(CPTableView)aTableView acceptDrop:(id <CPDraggingInfo>)theInfo row:(int)theRow dropOperation:(CPTableViewDropOperation)theOperation
1781 {
1782  if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_acceptDrop_item_childIndex_))
1783  return NO;
1784 
1785  var location = [_outlineView convertPoint:[theInfo draggingLocation] fromView:nil],
1786  parentItem = [self _parentItemForDropOperation:theOperation row:theRow offset:location],
1787  childIndex = [self _childIndexForDropOperation:theOperation row:theRow offset:location];
1788 
1789  _outlineView._retargetedItem = nil;
1790  _outlineView._shouldRetargetItem = NO;
1791 
1792  _outlineView._retargedChildIndex = nil;
1793  _outlineView._shouldRetargetChildIndex = NO;
1794 
1795  return [_outlineView._outlineViewDataSource outlineView:_outlineView acceptDrop:theInfo item:parentItem childIndex:childIndex];
1796 }
1797 
1798 - (void)tableView:(CPTableView)aTableView sortDescriptorsDidChange:(CPArray)oldSortDescriptors
1799 {
1800  if ((_outlineView._implementedOutlineViewDataSourceMethods &
1802  {
1803  [[_outlineView dataSource] outlineView:_outlineView sortDescriptorsDidChange:oldSortDescriptors];
1804  }
1805 }
1806 
1807 @end
1808 
1809 @implementation _CPOutlineViewTableViewDelegate : CPObject
1810 {
1811  CPOutlineView _outlineView;
1812 }
1813 
1814 - (id)initWithOutlineView:(CPOutlineView)anOutlineView
1815 {
1816  self = [super init];
1817 
1818  if (self)
1819  _outlineView = anOutlineView;
1820 
1821  return self;
1822 }
1823 
1824 - (CPView)tableView:(CPTableView)theTableView dataViewForTableColumn:(CPTableColumn)theTableColumn row:(int)theRow
1825 {
1826  var dataView = nil;
1827 
1828  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_dataViewForTableColumn_item_))
1829  dataView = [_outlineView._outlineViewDelegate outlineView:_outlineView
1830  dataViewForTableColumn:theTableColumn
1831  item:[_outlineView itemAtRow:theRow]];
1832 
1833  if (!dataView)
1834  dataView = [theTableColumn dataViewForRow:theRow];
1835 
1836  return dataView;
1837 }
1838 
1839 - (BOOL)tableView:(CPTableView)theTableView shouldSelectRow:(int)theRow
1840 {
1841  return SHOULD_SELECT_ITEM(_outlineView, [_outlineView itemAtRow:theRow]);
1842 }
1843 
1844 - (BOOL)selectionShouldChangeInTableView:(CPTableView)theTableView
1845 {
1846  return SELECTION_SHOULD_CHANGE(_outlineView);
1847 }
1848 
1849 - (BOOL)tableView:(CPTableView)aTableView shouldEditTableColumn:(CPTableColumn)aColumn row:(int)aRow
1850 {
1851  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldEditTableColumn_item_))
1852  return [_outlineView._outlineViewDelegate outlineView:_outlineView shouldEditTableColumn:aColumn item:[_outlineView itemAtRow:aRow]];
1853 
1854  return NO;
1855 }
1856 
1857 - (float)tableView:(CPTableView)theTableView heightOfRow:(int)theRow
1858 {
1859  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_heightOfRowByItem_))
1860  return [_outlineView._outlineViewDelegate outlineView:_outlineView heightOfRowByItem:[_outlineView itemAtRow:theRow]];
1861 
1862  return [theTableView rowHeight];
1863 }
1864 
1865 - (void)tableView:(CPTableView)aTableView willDisplayView:(id)aView forTableColumn:(CPTableColumn)aTableColumn row:(int)aRowIndex
1866 {
1867  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_willDisplayView_forTableColumn_item_))
1868  {
1869  var item = [_outlineView itemAtRow:aRowIndex];
1870  [_outlineView._outlineViewDelegate outlineView:_outlineView willDisplayView:aView forTableColumn:aTableColumn item:item];
1871  }
1872 }
1873 
1874 - (BOOL)tableView:(CPTableView)aTableView isGroupRow:(int)aRow
1875 {
1876  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_isGroupItem_))
1877  return [_outlineView._outlineViewDelegate outlineView:_outlineView isGroupItem:[_outlineView itemAtRow:aRow]];
1878 
1879  return NO;
1880 }
1881 
1882 - (CPMenu)tableView:(CPTableView)aTableView menuForTableColumn:(CPTableColumn)aTableColumn row:(int)aRow
1883 {
1884  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_menuForTableColumn_item_))
1885  {
1886  var item = [_outlineView itemAtRow:aRow];
1887  return [_outlineView._outlineViewDelegate outlineView:_outlineView menuForTableColumn:aTableColumn item:item]
1888  }
1889 
1890  // We reimplement CPView menuForEvent: because we can't call it directly. CPTableView implements menuForEvent:
1891  // to call this delegate method.
1892  return [_outlineView menu] || [[_outlineView class] defaultMenu];
1893 }
1894 
1895 @end
1896 
1897 @implementation CPDisclosureButton : CPButton
1898 {
1899  float _angle;
1900 }
1901 
1902 - (id)initWithFrame:(CGRect)aFrame
1903 {
1904  self = [super initWithFrame:aFrame];
1905 
1906  if (self)
1907  [self setBordered:NO];
1908 
1909  return self;
1910 }
1911 
1912 - (void)setState:(CPState)aState
1913 {
1914  [super setState:aState];
1915 
1916  if ([self state] === CPOnState)
1917  _angle = 0.0;
1918 
1919  else
1920  _angle = -PI_2;
1921 }
1922 
1923 - (void)drawRect:(CGRect)aRect
1924 {
1925  var bounds = [self bounds],
1927  width = _CGRectGetWidth(bounds),
1928  height = _CGRectGetHeight(bounds);
1929 
1930  CGContextBeginPath(context);
1931 
1932  if (_angle)
1933  {
1934  var centre = _CGPointMake(FLOOR(width / 2.0), FLOOR(height / 2.0));
1935  CGContextTranslateCTM(context, centre.x, centre.y);
1936  CGContextRotateCTM(context, _angle);
1937  CGContextTranslateCTM(context, -centre.x, -centre.y);
1938  }
1939 
1940  // Center, but crisp.
1941  CGContextTranslateCTM(context, FLOOR((width - 9.0) / 2.0), FLOOR((height - 8.0) / 2.0));
1942 
1943  CGContextMoveToPoint(context, 0.0, 0.0);
1944  CGContextAddLineToPoint(context, 9.0, 0.0);
1945  CGContextAddLineToPoint(context, 4.5, 8.0);
1946  CGContextAddLineToPoint(context, 0.0, 0.0);
1947 
1948  CGContextClosePath(context);
1949  CGContextSetFillColor(context,
1950  colorForDisclosureTriangle([self hasThemeState:CPThemeStateSelected],
1951  [self hasThemeState:CPThemeStateHighlighted]));
1952  CGContextFillPath(context);
1953 
1954  CGContextBeginPath(context);
1955  CGContextMoveToPoint(context, 0.0, 0.0);
1956 
1957  CGContextAddLineToPoint(context, 4.5, 8.0);
1958  if (_angle === 0.0)
1959  CGContextAddLineToPoint(context, 9.0, 0.0);
1960 
1961  CGContextSetStrokeColor(context, [CPColor colorWithCalibratedWhite:1.0 alpha: 0.7]);
1962  CGContextStrokePath(context);
1963 }
1964 
1965 @end
1966 
1967 
1968 var CPOutlineViewIndentationPerLevelKey = @"CPOutlineViewIndentationPerLevelKey",
1969  CPOutlineViewOutlineTableColumnKey = @"CPOutlineViewOutlineTableColumnKey",
1970  CPOutlineViewDataSourceKey = @"CPOutlineViewDataSourceKey",
1971  CPOutlineViewDelegateKey = @"CPOutlineViewDelegateKey";
1972 
1974 
1975 - (id)initWithCoder:(CPCoder)aCoder
1976 {
1977  self = [super initWithCoder:aCoder];
1978 
1979  if (self)
1980  {
1981  // The root item has weight "0", thus represents the weight solely of its descendants.
1982  _rootItemInfo = { isExpanded:YES, isExpandable:NO, level:-1, row:-1, children:[], weight:0 };
1983 
1984  _itemsForRows = [];
1985  _itemInfosForItems = { };
1986  _disclosureControlsForRows = [];
1987 
1989  [self setDisclosureControlPrototype:[[CPDisclosureButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 10.0)]];
1990 
1991  _outlineTableColumn = [aCoder decodeObjectForKey:CPOutlineViewOutlineTableColumnKey];
1992  _indentationPerLevel = [aCoder decodeFloatForKey:CPOutlineViewIndentationPerLevelKey];
1993 
1994  _outlineViewDataSource = [aCoder decodeObjectForKey:CPOutlineViewDataSourceKey];
1995  _outlineViewDelegate = [aCoder decodeObjectForKey:CPOutlineViewDelegateKey];
1996 
1997  [super setDataSource:[[_CPOutlineViewTableViewDataSource alloc] initWithOutlineView:self]];
1998  [super setDelegate:[[_CPOutlineViewTableViewDelegate alloc] initWithOutlineView:self]];
1999  }
2000 
2001  return self;
2002 }
2003 
2004 - (void)encodeWithCoder:(CPCoder)aCoder
2005 {
2006  // Make sure we don't encode our internal delegate and data source.
2007  var internalDelegate = _delegate,
2008  internalDataSource = _dataSource;
2009  _delegate = nil;
2010  _dataSource = nil;
2011  [super encodeWithCoder:aCoder];
2012  _delegate = internalDelegate;
2013  _dataSource = internalDataSource;
2014 
2015  [aCoder encodeObject:_outlineTableColumn forKey:CPOutlineViewOutlineTableColumnKey];
2016  [aCoder encodeFloat:_indentationPerLevel forKey:CPOutlineViewIndentationPerLevelKey];
2017 
2018  [aCoder encodeObject:_outlineViewDataSource forKey:CPOutlineViewDataSourceKey];
2019  [aCoder encodeObject:_outlineViewDelegate forKey:CPOutlineViewDelegateKey];
2020 }
2021 
2022 @end
2023 
2024 
2025 var colorForDisclosureTriangle = function(isSelected, isHighlighted)
2026 {
2027  return isSelected
2028  ? (isHighlighted
2030  : [CPColor colorWithCalibratedWhite:1.0 alpha: 1.0])
2031  : (isHighlighted
2032  ? [CPColor colorWithCalibratedWhite:0.4 alpha: 1.0]
2033  : [CPColor colorWithCalibratedWhite:0.5 alpha: 1.0]);
2034 };