API  0.9.10
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 
78 
80 
84 
85 #define SELECTION_SHOULD_CHANGE(anOutlineView) (!((anOutlineView)._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_selectionShouldChangeInOutlineView_) || [(anOutlineView)._outlineViewDelegate selectionShouldChangeInOutlineView:(anOutlineView)])
86 
87 #define SHOULD_SELECT_ITEM(anOutlineView, anItem) (!((anOutlineView)._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectItem_) || [(anOutlineView)._outlineViewDelegate outlineView:(anOutlineView) shouldSelectItem:(anItem)])
88 
89 
90 @protocol CPOutlineViewDelegate <CPObject>
91 
92 @optional
93 - (BOOL)outlineView:(CPOutlineView)anOutlineView isGroupItem:(id)anItem;
94 - (BOOL)outlineView:(CPOutlineView)anOutlineView shouldCollapseItem:(id)anItem;
95 - (BOOL)outlineView:(CPOutlineView)anOutlineView shouldEditTableColumn:(CPTableColumn)aTableColumn item:(id)anItem;
96 - (BOOL)outlineView:(CPOutlineView)anOutlineView shouldExpandItem:(id)anItem;
97 - (BOOL)outlineView:(CPOutlineView)anOutlineView shouldReorderColumn:(CPInteger)columnIndex toColumn:(CPInteger)newColumnIndex;
98 - (BOOL)outlineView:(CPOutlineView)anOutlineView shouldSelectItem:(id)anItem;
99 - (BOOL)outlineView:(CPOutlineView)anOutlineView shouldSelectTableColumn:(CPTableColumn)aTableColumn;
100 - (BOOL)outlineView:(CPOutlineView)anOutlineView shouldShowOutlineDisclosureControlForItem:(id)anItem;
101 - (BOOL)outlineView:(CPOutlineView)anOutlineView shouldShowViewExpansionForTableColumn:(CPTableColumn)aTableColumn item:(id)anItem;
102 - (BOOL)outlineView:(CPOutlineView)anOutlineView shouldTrackView:(CPView)aView forTableColumn:(CPTableColumn)aTableColumn item:(id)anItem;
103 - (BOOL)outlineView:(CPOutlineView)anOutlineView shouldTypeSelectForEvent:(CPEvent)anEvent withCurrentSearchString:(CPString)searchString;
104 - (BOOL)selectionShouldChangeInOutlineView:(CPOutlineView)anOutlineView;
105 - (CPIndexSet)outlineView:(CPOutlineView)anOutlineView selectionIndexesForProposedSelection:(CPIndexSet)proposedSelectionIndexes;
106 - (CPMenu)outlineView:(CPOutlineView)anOutlineView menuForTableColumn:(CPTableColumn)aTableColumn item:(id)anItem;
107 - (CPString)outlineView:(CPOutlineView)anOutlineView toolTipForView:(CPView)aView rect:(CGRect)aRect tableColumn:(CPTableColumn)aTableColumn item:(id)anItem mouseLocation:(CGPoint)mouseLocation;
108 - (CPString)outlineView:(CPOutlineView)anOutlineView typeSelectStringForTableColumn:(CPTableColumn)aTableColumn item:(id)anItem;
109 - (CPView)outlineView:(CPOutlineView)anOutlineView dataViewForTableColumn:(CPTableColumn)aTableColumn item:(id)anItem;
110 - (CPView)outlineView:(CPOutlineView)anOutlineView viewForTableColumn:(CPTableColumn)aTableColumn item:(id)anItem;
111 - (float)outlineView:(CPOutlineView)anOutlineView heightOfRowByItem:(id)anItem;
112 - (float)outlineView:(CPOutlineView)anOutlineView sizeToFitWidthOfColumn:(CPTableColumn)aTableColumn;
113 - (id)outlineView:(CPOutlineView)anOutlineView nextTypeSelectMatchFromItem:(id)startItem toItem:(id)endItem forString:(CPString)searchString;
114 - (void)outlineView:(CPOutlineView)anOutlineView didClickTableColumn:(CPTableColumn)aTableColumn;
115 - (void)outlineView:(CPOutlineView)anOutlineView didDragTableColumn:(CPTableColumn)aTableColumn;
116 - (void)outlineView:(CPOutlineView)anOutlineView mouseDownInHeaderOfTableColumn:(CPTableColumn)aTableColumn;
117 - (void)outlineView:(CPOutlineView)anOutlineView willDisplayOutlineView:(CPView)aView forTableColumn:(CPTableColumn)aTableColumn item:(id)anItem;
118 - (void)outlineView:(CPOutlineView)anOutlineView willDisplayView:(CPView)aView forTableColumn:(CPTableColumn)aTableColumn item:(id)anItem;
119 - (void)outlineView:(CPOutlineView)anOutlineView willRemoveView:(CPView)aView forTableColumn:(CPTableColumn)aTableColumn item:(id)anItem;
120 
121 @end
122 
123 
124 @protocol CPOutlineViewDataSource <CPObject>
125 
126 @optional
127 - (BOOL)outlineView:(CPOutlineView)anOutlineView acceptDrop:(id /*<CPDraggingInfo>*/)info item:(id)anItem childIndex:(CPInteger)anIndex;
128 - (BOOL)outlineView:(CPOutlineView)anOutlineView shouldDeferDisplayingChildrenOfItem:(id)anItem;
129 - (BOOL)outlineView:(CPOutlineView)anOutlineView writeItems:(CPArray)items toPasteboard:(CPPasteboard)pboard;
130 - (CPArray)outlineView:(CPOutlineView)anOutlineView namesOfPromisedFilesDroppedAtDestination:(CPURL)dropDestination forDraggedItems:(CPArray)items;
131 - (CPDragOperation)outlineView:(CPOutlineView)anOutlineView validateDrop:(id /*<CPDraggingInfo>*/)info proposedItem:(id)anItem proposedChildIndex:(CPInteger)anIndex;
132 - (CPDragOperation)outlineView:(CPOutlineView)anOutlineView validateDrop:(id /*<CPDraggingInfo>*/)info proposedRow:(int)theRow proposedDropOperation:(CPTableViewDropOperation)theOperation;
133 - (id)outlineView:(CPOutlineView)anOutlineView itemForPersistentObject:(id)anObject;
134 - (id)outlineView:(CPOutlineView)anOutlineView objectValueforTableColumn:(CPTableColumn)aTableColumn byItem:(id)anItem;
135 - (id)outlineView:(CPOutlineView)anOutlineView persistentObjectForItem:(id)anItem;
136 - (void)outlineView:(CPOutlineView)anOutlineView setObjectValue:(id)anObject forTableColumn:(CPTableColumn)aTableColumn byItem:(id)anItem;
137 - (void)outlineView:(CPOutlineView)anOutlineView sortDescriptorsDidChange:(CPArray)oldDescriptors;
138 
139 @end
140 
155 @implementation CPOutlineView : CPTableView
156 {
157  id <CPOutlineViewDataSource> _outlineViewDataSource;
158  id <CPOutlineViewDelegate> _outlineViewDelegate;
159  CPTableColumn _outlineTableColumn;
160 
161  float _indentationPerLevel;
162  BOOL _indentationMarkerFollowsDataView;
163 
164  CPInteger _implementedOutlineViewDataSourceMethods;
165  CPInteger _implementedOutlineViewDelegateMethods;
166 
167  Object _rootItemInfo;
168  CPMutableArray _itemsForRows;
169  Object _itemInfosForItems;
170 
171  CPControl _disclosureControlPrototype;
172  CPArray _disclosureControlsForRows;
173  CPData _disclosureControlData;
174  CPArray _disclosureControlQueue;
175 
176  BOOL _shouldRetargetItem;
177  id _retargetedItem;
178 
179  BOOL _shouldRetargetChildIndex;
180  CPInteger _retargedChildIndex;
181  CPTimer _dragHoverTimer;
182  id _dropItem;
183 
184  BOOL _coalesceSelectionNotificationState;
185 
186  CPArray _pendingItemToClean;
187  CPArray _itemAddedDuringLastLoading;
188 }
189 
190 - (id)initWithFrame:(CGRect)aFrame
191 {
192  self = [super initWithFrame:aFrame];
193 
194  if (self)
195  {
196  _selectionHighlightStyle = CPTableViewSelectionHighlightStyleSourceList;
197 
198  // The root item has weight "0", thus represents the weight solely of its descendants.
199  _rootItemInfo = { isExpanded:YES, isExpandable:NO, shouldShowOutlineDisclosureControl:NO, level:-1, row:-1, children:[], weight:0 };
200 
201  _itemsForRows = [];
202  _itemInfosForItems = { };
203  _disclosureControlsForRows = [];
204 
205  _retargetedItem = nil;
206  _shouldRetargetItem = NO;
207 
208  _retargedChildIndex = nil;
209  _shouldRetargetChildIndex = NO;
210 
211  [self setIndentationPerLevel:16.0];
213 
214  [super setDataSource:[[_CPOutlineViewTableViewDataSource alloc] initWithOutlineView:self]];
215  [super setDelegate:[[_CPOutlineViewTableViewDelegate alloc] initWithOutlineView:self]];
216 
217  [self setDisclosureControlPrototype:[[CPDisclosureButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 10.0)]];
218  }
219 
220  return self;
221 }
222 
223 - (void)_initSubclass
224 {
225  _BlockDeselectView = function(view, row, column)
226  {
227  [view unsetThemeState:CPThemeStateSelectedDataView];
228  [_disclosureControlsForRows[row] unsetThemeState:CPThemeStateSelected];
229  };
230 
231  _BlockSelectView = function(view, row, column)
232  {
233  [view setThemeState:CPThemeStateSelectedDataView];
234  [_disclosureControlsForRows[row] setThemeState:CPThemeStateSelected];
235  };
236 }
237 
289 - (void)setDataSource:(id <CPOutlineViewDataSource>)aDataSource
290 {
291  if (_outlineViewDataSource === aDataSource)
292  return;
293 
294  if (![aDataSource respondsToSelector:@selector(outlineView:child:ofItem:)])
295  [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:child:ofItem:'"];
296 
297  if (![aDataSource respondsToSelector:@selector(outlineView:isItemExpandable:)])
298  [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:isItemExpandable:'"];
299 
300  if (![aDataSource respondsToSelector:@selector(outlineView:numberOfChildrenOfItem:)])
301  [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:numberOfChildrenOfItem:'"];
302 
303  _outlineViewDataSource = aDataSource;
304  _implementedOutlineViewDataSourceMethods = 0;
305 
306  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:objectValueForTableColumn:byItem:)])
307  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_objectValue_forTableColumn_byItem_;
308 
309  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:setObjectValue:forTableColumn:byItem:)])
310  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_setObjectValue_forTableColumn_byItem_;
311 
312  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:shouldDeferDisplayingChildrenOfItem:)])
313  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_shouldDeferDisplayingChildrenOfItem_;
314 
315  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:acceptDrop:item:childIndex:)])
316  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_acceptDrop_item_childIndex_;
317 
318  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:validateDrop:proposedItem:proposedChildIndex:)])
320 
321  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:validateDrop:proposedRow:proposedDropOperation:)])
323 
324  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:namesOfPromisedFilesDroppedAtDestination:forDraggedItems:)])
326 
327  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:itemForPersistentObject:)])
328  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_itemForPersistentObject_;
329 
330  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:persistentObjectForItem:)])
331  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_persistentObjectForItem_;
332 
333  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:writeItems:toPasteboard:)])
334  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_writeItems_toPasteboard_;
335 
336  if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:sortDescriptorsDidChange:)])
337  _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_sortDescriptorsDidChange_;
338 
339  [self reloadData];
340 }
341 
348 {
349  return _outlineViewDataSource;
350 }
351 
359 - (BOOL)isExpandable:(id)anItem
360 {
361  if (!anItem)
362  return YES;
363 
364  var itemInfo = [self _itemInfosForItem:anItem];
365 
366  if (!itemInfo)
367  return NO;
368 
369  return itemInfo.isExpandable;
370 }
371 
372 - (BOOL)_shouldShowOutlineDisclosureControlForItem:(id)anItem
373 {
374  if (!anItem)
375  return YES;
376 
377  var itemInfo = _itemInfosForItems[[anItem UID]];
378 
379  if (!itemInfo)
380  return YES;
381 
382  return itemInfo.shouldShowOutlineDisclosureControl;
383 }
384 
392 - (BOOL)isItemExpanded:(id)anItem
393 {
394  if (!anItem)
395  return YES;
396 
397  var itemInfo = [self _itemInfosForItem:anItem];
398 
399  if (!itemInfo)
400  return NO;
401 
402  return itemInfo.isExpanded;
403 }
404 
411 - (id)itemAtRow:(CPInteger)aRow
412 {
413  return _itemsForRows[aRow] || nil;
414 }
415 
422 - (CPInteger)rowForItem:(id)anItem
423 {
424  if (!anItem)
425  return _rootItemInfo.row;
426 
427  var itemInfo = [self _itemInfosForItem:anItem];
428 
429  if (!itemInfo)
430  return CPNotFound;
431 
432  return itemInfo.row;
433 }
434 
443 - (CPInteger)levelForItem:(id)anItem
444 {
445  if (!anItem)
446  return _rootItemInfo.level;
447 
448  var itemInfo = [self _itemInfosForItem:anItem];
449 
450  if (!itemInfo)
451  return CPNotFound;
452 
453  return itemInfo.level;
454 }
455 
463 - (CPInteger)levelForRow:(CPInteger)aRow
464 {
465  var item = [self itemAtRow:aRow];
466 
467  if (!item && aRow >= 0)
468  item = [CPObject new];
469 
470  return [self levelForItem:item];
471 }
472 
478 - (Object)_itemInfosForItem:(id)anItem
479 {
480  var itemInfo = _itemInfosForItems[[anItem UID]];
481 
482  if (!itemInfo || [self _parentIsCollapsed:[self parentForItem:anItem]])
483  return nil;
484 
485  return itemInfo;
486 }
487 
492 - (BOOL)_parentIsCollapsed:(id)anItem
493 {
494  var parentItem = [self parentForItem:anItem],
495  itemInfo = _itemInfosForItems[[anItem UID]];
496 
497  if (!itemInfo)
498  return NO;
499 
500  if (!itemInfo.isExpanded)
501  return YES;
502 
503  return [self _parentIsCollapsed:parentItem];
504 }
505 
511 - (void)expandItem:(id)anItem
512 {
513  [self expandItem:anItem expandChildren:NO];
514 }
515 
522 - (void)expandItem:(id)anItem expandChildren:(BOOL)shouldExpandChildren
523 {
524  if ([self _delegateRespondsToShouldExpandItem])
525  if ([_outlineViewDelegate outlineView:self shouldExpandItem:anItem] == NO)
526  return;
527 
528  var itemInfo = null;
529 
530  if (!anItem)
531  itemInfo = _rootItemInfo;
532  else
533  itemInfo = _itemInfosForItems[[anItem UID]];
534 
535  if (!itemInfo)
536  return;
537 
538  // When shouldExpandChildren is YES, we need to make sure we're collecting
539  // selection notifications so that exactly one IsChanging and one
540  // DidChange is sent as needed, for the totality of the operation.
541  var isTopLevel = NO;
542 
543  if (!_coalesceSelectionNotificationState)
544  {
545  isTopLevel = YES;
546  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOn;
547  }
548 
549  // To prevent items which are already expanded from firing notifications.
550  if (!itemInfo.isExpanded)
551  {
552  [self _noteItemWillExpand:anItem];
553 
554  var previousRowCount = [self numberOfRows];
555 
556  itemInfo.isExpanded = YES;
557  [self reloadItem:anItem reloadChildren:YES];
558  [self _noteItemDidExpand:anItem];
559 
560  // Shift selection indexes below so that the same items remain selected.
561  var rowCountDelta = [self numberOfRows] - previousRowCount;
562 
563  if (rowCountDelta)
564  {
565  var selection = [self selectedRowIndexes],
566  expandIndex = [self rowForItem:anItem] + 1;
567 
568  if ([selection intersectsIndexesInRange:CPMakeRange(expandIndex, _itemsForRows.length)])
569  {
570  [self _noteSelectionIsChanging];
571  [selection shiftIndexesStartingAtIndex:expandIndex by:rowCountDelta];
572  [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
573  }
574  }
575  }
576 
577  if (shouldExpandChildren)
578  {
579  var children = itemInfo.children,
580  childIndex = children.length;
581 
582  while (childIndex--)
583  [self expandItem:children[childIndex] expandChildren:YES];
584  }
585 
586  if (isTopLevel)
587  {
588  var r = _coalesceSelectionNotificationState;
589  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOff;
591  [self _noteSelectionDidChange];
592  }
593 }
594 
600 - (void)collapseItem:(id)anItem
601 {
602  if (!anItem)
603  return;
604 
605  if ([self _delegateRespondsToShouldCollapseItem])
606  if ([_outlineViewDelegate outlineView:self shouldCollapseItem:anItem] == NO)
607  return;
608 
609  var itemInfo = _itemInfosForItems[[anItem UID]];
610 
611  if (!itemInfo)
612  return;
613 
614  if (!itemInfo.isExpanded)
615  return;
616 
617  // Don't spam notifications.
618  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOn;
619 
620  [self _noteItemWillCollapse:anItem];
621  // Update selections:
622  // * Deselect items inside the collapsed item.
623  // * Shift row selections below the collapsed item so that the same logical items remain selected.
624  var collapseTopIndex = [self rowForItem:anItem],
625  topLevel = [self levelForRow:collapseTopIndex],
626  collapseEndIndex = collapseTopIndex;
627 
628  while (collapseEndIndex + 1 < _itemsForRows.length && [self levelForRow:collapseEndIndex + 1] > topLevel)
629  collapseEndIndex++;
630 
631  var collapseRange = CPMakeRange(collapseTopIndex + 1, collapseEndIndex - collapseTopIndex);
632 
633  if (collapseRange.length)
634  {
635  var selection = [self selectedRowIndexes];
636 
637  if ([selection intersectsIndexesInRange:collapseRange])
638  {
639  [self _noteSelectionIsChanging];
640  [selection removeIndexesInRange:collapseRange];
641  [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
642  }
643 
644  // Shift any selected rows below upwards.
645  if ([selection intersectsIndexesInRange:CPMakeRange(collapseEndIndex + 1, _itemsForRows.length)])
646  {
647  [self _noteSelectionIsChanging];
648  [selection shiftIndexesStartingAtIndex:collapseEndIndex + 1 by:-collapseRange.length];
649  [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
650  }
651  }
652 
653  itemInfo.isExpanded = NO;
654 
655  [self reloadItem:anItem reloadChildren:YES];
656  [self _noteItemDidCollapse:anItem];
657 
658  // Send selection notifications only after the items have loaded so that
659  // the new selection is consistent with the actual rows for any observers.
660  var r = _coalesceSelectionNotificationState;
661  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOff;
663  [self _noteSelectionDidChange];
664 }
665 
671 - (void)reloadItem:(id)anItem
672 {
673  [self reloadItem:anItem reloadChildren:NO];
674 }
675 
682 - (void)reloadItem:(id)anItem reloadChildren:(BOOL)shouldReloadChildren
683 {
684  _pendingItemToClean = [];
685  _itemAddedDuringLastLoading = [];
686 
687  if (!!shouldReloadChildren || !anItem)
688  [self _loadItemInfoForItem:anItem intermediate:NO];
689  else
690  [self _reloadItem:anItem];
691 
692  [self _cleanPendingItem];
693 
694  [super _reloadDataViews];
695 }
696 
697 - (void)_reloadItem:(id)anItem
698 {
699  if (!anItem)
700  return;
701 
702  // Get the existing info if it exists.
703  var itemUID = [anItem UID],
704  itemInfo = _itemInfosForItems[itemUID];
705 
706  // If we're not in the tree, then just bail.
707  if (!itemInfo)
708  return [];
709 
710  // See if the item itself can be swapped out.
711  var parent = itemInfo.parent,
712  parentItemInfo = parent ? _itemInfosForItems[[parent UID]] : _rootItemInfo,
713  parentChildren = parentItemInfo.children,
714  index = [parentChildren indexOfObjectIdenticalTo:anItem],
715  newItem = [_outlineViewDataSource outlineView:self child:index ofItem:parent];
716 
717  if (anItem !== newItem)
718  {
719  _itemInfosForItems[[anItem UID]] = nil;
720  _itemInfosForItems[[newItem UID]] = itemInfo;
721 
722  parentChildren[index] = newItem;
723  _itemsForRows[itemInfo.row] = newItem;
724  }
725 
726  itemInfo.isExpandable = [_outlineViewDataSource outlineView:self isItemExpandable:newItem];
727  itemInfo.isExpanded = itemInfo.isExpandable && itemInfo.isExpanded;
728  itemInfo.shouldShowOutlineDisclosureControl = [self _sendDelegateShouldShowOutlineDisclosureControlForItem:newItem];
729 }
730 
731 - (void)_addPendingItemsFromPreviousItems:(CPArray)previousItems forItemInfo:(Object)itemInfo
732 {
733  if (!itemInfo)
734  return;
735 
736  var children = itemInfo.children;
737 
738  for (var i = [previousItems count] - 1; i >= 0; i--)
739  {
740  var item = previousItems[i];
741 
742  if (![children containsObject:item])
743  [self _addPendingItem:item];
744  }
745 }
746 
747 - (void)_addPendingItem:(id)anItem
748 {
749  var itemInfo = _itemInfosForItems[[anItem UID]];
750 
751  if (!itemInfo)
752  return;
753 
754  var children = itemInfo.children;
755 
756  for (var i = [children count]; i >= 0; i--)
757  {
758  var child = children[i];
759  [self _addPendingItem:child];
760  }
761 
762  [_pendingItemToClean addObject:anItem];
763 }
764 
765 - (void)_cleanPendingItem
766 {
767  for (var i = [_pendingItemToClean count]; i >= 0; i--)
768  {
769  var item = _pendingItemToClean[i];
770 
771  if (![_itemAddedDuringLastLoading containsObject:item])
772  delete _itemInfosForItems[[item UID]];
773  }
774 
775  _pendingItemToClean = [];
776  _itemAddedDuringLastLoading = [];
777 }
778 
779 - (CPArray)_loadItemInfoForItem:(id)anItem intermediate:(BOOL)isIntermediate
780 {
781  if (!anItem)
782  {
783  var itemInfo = _rootItemInfo;
784  }
785  else
786  {
787  // Get the existing info if it exists.
788  var itemUID = [anItem UID],
789  itemInfo = _itemInfosForItems[itemUID];
790 
791  // If we're not in the tree, then just bail.
792  if (!itemInfo)
793  return [];
794 
795  itemInfo.isExpandable = [_outlineViewDataSource outlineView:self isItemExpandable:anItem];
796  itemInfo.shouldShowOutlineDisclosureControl = [self _sendDelegateShouldShowOutlineDisclosureControlForItem:anItem];
797 
798  // If we were previously expanded, but now no longer expandable, "de-expand".
799  // NOTE: we are *not* collapsing, thus no notification is posted.
800  if (!itemInfo.isExpandable && itemInfo.isExpanded)
801  {
802  itemInfo.isExpanded = NO;
803  itemInfo.children = [];
804  }
805  }
806 
807  // The root item does not count as a descendant.
808  var weight = itemInfo.weight,
809  descendants = anItem ? [anItem] : [];
810 
811  [_itemAddedDuringLastLoading addObject:anItem];
812 
813  if (itemInfo.isExpanded && [self _sendDataSourceShouldDeferDisplayingChildrenOfItem:anItem])
814  {
815  var index = 0,
816  count = [_outlineViewDataSource outlineView:self numberOfChildrenOfItem:anItem],
817  level = itemInfo.level + 1,
818  previousChildren = itemInfo.children;
819 
820  itemInfo.children = [];
821 
822  for (; index < count; ++index)
823  {
824  var childItem = [_outlineViewDataSource outlineView:self child:index ofItem:anItem],
825  childItemInfo = _itemInfosForItems[[childItem UID]];
826 
827  if (!childItemInfo)
828  {
829  childItemInfo = { isExpanded:NO, isExpandable:NO, shouldShowOutlineDisclosureControl:YES, children:[], weight:1 };
830  _itemInfosForItems[[childItem UID]] = childItemInfo;
831  }
832 
833  itemInfo.children[index] = childItem;
834 
835  var childDescendants = [self _loadItemInfoForItem:childItem intermediate:YES];
836 
837  childItemInfo.parent = anItem;
838  childItemInfo.level = level;
839  descendants = descendants.concat(childDescendants);
840  }
841 
842  // Here we clean the itemInfos dictionary
843  // Some items could have been removed at this point, we don't need to keep a ref of them anymore
844  [self _addPendingItemsFromPreviousItems:previousChildren forItemInfo:itemInfo];
845  }
846 
847  itemInfo.weight = descendants.length;
848 
849  if (!isIntermediate)
850  {
851  // row = -1 is the root item, so just go to row 0 since it is ignored.
852  var index = MAX(itemInfo.row, 0);
853 
854  descendants.unshift(index, weight);
855 
856  _itemsForRows.splice.apply(_itemsForRows, descendants);
857 
858  var count = _itemsForRows.length;
859 
860  for (; index < count; ++index)
861  _itemInfosForItems[[_itemsForRows[index] UID]].row = index;
862 
863  var deltaWeight = itemInfo.weight - weight;
864 
865  if (deltaWeight !== 0)
866  {
867  var parent = itemInfo.parent;
868 
869  while (parent)
870  {
871  var parentItemInfo = _itemInfosForItems[[parent UID]];
872 
873  parentItemInfo.weight += deltaWeight;
874  parent = parentItemInfo.parent;
875  }
876 
877  if (anItem)
878  _rootItemInfo.weight += deltaWeight;
879  }
880  }
881 
882  return descendants;
883 }
884 
891 - (void)setOutlineTableColumn:(CPTableColumn)aTableColumn
892 {
893  if (_outlineTableColumn === aTableColumn)
894  return;
895 
896  _outlineTableColumn = aTableColumn;
897 
898  // FIXME: efficiency.
899  [self reloadData];
900 }
901 
908 {
909  return _outlineTableColumn;
910 }
911 
917 - (void)setIndentationPerLevel:(float)anIndentationWidth
918 {
919  if (_indentationPerLevel === anIndentationWidth)
920  return;
921 
922  _indentationPerLevel = anIndentationWidth;
923 
924  // FIXME: efficiency!!!!
925  [self reloadData];
926 }
927 
934 {
935  return _indentationPerLevel;
936 }
937 
945 - (void)setIndentationMarkerFollowsDataView:(BOOL)indentationMarkerShouldFollowDataView
946 {
947  if (_indentationMarkerFollowsDataView === indentationMarkerShouldFollowDataView)
948  return;
949 
950  _indentationMarkerFollowsDataView = indentationMarkerShouldFollowDataView;
951 
952  // !!!!
953  [self reloadData];
954 }
955 
964 {
965  return _indentationMarkerFollowsDataView;
966 }
967 
975 - (id)parentForItem:(id)anItem
976 {
977  if (!anItem)
978  return nil;
979 
980  var itemInfo = _itemInfosForItems[[anItem UID]];
981 
982  if (!itemInfo)
983  return nil;
984 
985  var parent = itemInfo.parent;
986 
987  // Check if the parent is the root item because we never return the actual root item
988  if (itemInfo[[parent UID]] === _rootItemInfo)
989  parent = nil;
990 
991  return parent;
992 }
993 
999 - (CGRect)_frameOfOutlineDataViewAtRow:(CPInteger)aRow
1000 {
1001  var columnIndex = [[self tableColumns] indexOfObject:_outlineTableColumn],
1002  frame = [super frameOfDataViewAtColumn:columnIndex row:aRow],
1003  indentationWidth = ([self levelForRow:aRow] + 1) * [self indentationPerLevel];
1004 
1005  frame.origin.x += indentationWidth;
1006  frame.size.width -= indentationWidth;
1007 
1008  return frame;
1009 }
1010 
1019 - (CGRect)frameOfOutlineDisclosureControlAtRow:(CPInteger)aRow
1020 {
1021  var theItem = [self itemAtRow:aRow];
1022 
1023  if (![self isExpandable:theItem] || ![self _shouldShowOutlineDisclosureControlForItem:theItem])
1024  return CGRectMakeZero();
1025 
1026  var dataViewFrame = [self _frameOfOutlineDataViewAtRow:aRow],
1027  disclosureWidth = CGRectGetWidth([_disclosureControlPrototype frame]),
1028  frame = CGRectMake(CGRectGetMinX(dataViewFrame) - disclosureWidth, CGRectGetMinY(dataViewFrame), disclosureWidth, CGRectGetHeight(dataViewFrame));
1029 
1030  return frame;
1031 }
1032 
1037 - (void)_setSelectedRowIndexes:(CPIndexSet)rows
1038 {
1039  if (_disclosureControlsForRows.length)
1040  {
1041  var indexes = [_selectedRowIndexes copy];
1042  [indexes removeIndexesInRange:CPMakeRange(_disclosureControlsForRows.length, _itemsForRows.length - _disclosureControlsForRows.length)];
1043  [[_disclosureControlsForRows objectsAtIndexes:indexes] makeObjectsPerformSelector:@selector(unsetThemeState:) withObject:CPThemeStateSelected];
1044  }
1045 
1046  [super _setSelectedRowIndexes:rows];
1047 
1048  if (_disclosureControlsForRows.length)
1049  {
1050  var indexes = [_selectedRowIndexes copy];
1051  [indexes removeIndexesInRange:CPMakeRange(_disclosureControlsForRows.length, _itemsForRows.length - _disclosureControlsForRows.length)];
1052  [[_disclosureControlsForRows objectsAtIndexes:indexes] makeObjectsPerformSelector:@selector(setThemeState:) withObject:CPThemeStateSelected];
1053  }
1054 }
1055 
1132 - (void)setDelegate:(id <CPOutlineViewDelegate>)aDelegate
1133 {
1134  if (_outlineViewDelegate === aDelegate)
1135  return;
1136 
1137  var defaultCenter = [CPNotificationCenter defaultCenter];
1138 
1139  if (_outlineViewDelegate)
1140  {
1141  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidMove:)])
1142  [defaultCenter
1143  removeObserver:_outlineViewDelegate
1145  object:self];
1146 
1147  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidResize:)])
1148  [defaultCenter
1149  removeObserver:_outlineViewDelegate
1151  object:self];
1152 
1153  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionDidChange:)])
1154  [defaultCenter
1155  removeObserver:_outlineViewDelegate
1157  object:self];
1158 
1159  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionIsChanging:)])
1160  [defaultCenter
1161  removeObserver:_outlineViewDelegate
1163  object:self];
1164 
1165  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillExpand:)])
1166  [defaultCenter
1167  removeObserver:_outlineViewDelegate
1169  object:self];
1170 
1171  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidExpand:)])
1172  [defaultCenter
1173  removeObserver:_outlineViewDelegate
1175  object:self];
1176 
1177  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillCollapse:)])
1178  [defaultCenter
1179  removeObserver:_outlineViewDelegate
1181  object:self];
1182 
1183  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidCollapse:)])
1184  [defaultCenter
1185  removeObserver:_outlineViewDelegate
1187  object:self];
1188  }
1189 
1190  _outlineViewDelegate = aDelegate;
1191  _implementedOutlineViewDelegateMethods = 0;
1192 
1193  var delegateMethods = [
1194  CPOutlineViewDelegate_outlineView_dataViewForTableColumn_item_ , @selector(outlineView:dataViewForTableColumn:item:),
1195  CPOutlineViewDelegate_outlineView_viewForTableColumn_item_ , @selector(outlineView:viewForTableColumn:item:),
1196  CPOutlineViewDelegate_outlineView_didClickTableColumn_ , @selector(outlineView:didClickTableColumn:),
1197  CPOutlineViewDelegate_outlineView_didDragTableColumn_ , @selector(outlineView:didDragTableColumn:),
1198  CPOutlineViewDelegate_outlineView_heightOfRowByItem_ , @selector(outlineView:heightOfRowByItem:),
1199  CPOutlineViewDelegate_outlineView_isGroupItem_ , @selector(outlineView:isGroupItem:),
1200  CPOutlineViewDelegate_outlineView_mouseDownInHeaderOfTableColumn_ , @selector(outlineView:mouseDownInHeaderOfTableColumn:),
1201  CPOutlineViewDelegate_outlineView_nextTypeSelectMatchFromItem_toItem_forString_ , @selector(outlineView:nextTypeSelectMatchFromItem:toItem:forString:),
1202  CPOutlineViewDelegate_outlineView_selectionIndexesForProposedSelection_ , @selector(outlineView:selectionIndexesForProposedSelection:),
1203  CPOutlineViewDelegate_outlineView_shouldCollapseItem_ , @selector(outlineView:shouldCollapseItem:),
1204  CPOutlineViewDelegate_outlineView_shouldEditTableColumn_item_ , @selector(outlineView:shouldEditTableColumn:item:),
1205  CPOutlineViewDelegate_outlineView_shouldExpandItem_ , @selector(outlineView:shouldExpandItem:),
1206  CPOutlineViewDelegate_outlineView_shouldReorderColumn_toColumn_ , @selector(outlineView:shouldReorderColumn:toColumn:),
1207  CPOutlineViewDelegate_outlineView_shouldSelectItem_ , @selector(outlineView:shouldSelectItem:),
1208  CPOutlineViewDelegate_outlineView_shouldSelectTableColumn_ , @selector(outlineView:shouldSelectTableColumn:),
1209  CPOutlineViewDelegate_outlineView_shouldShowOutlineDisclosureControlForItem_ , @selector(outlineView:shouldShowOutlineDisclosureControlForItem:),
1210  CPOutlineViewDelegate_outlineView_shouldShowViewExpansionForTableColumn_item_ , @selector(outlineView:shouldShowViewExpansionForTableColumn:item:),
1211  CPOutlineViewDelegate_outlineView_shouldTrackView_forTableColumn_item_ , @selector(outlineView:shouldTrackView:forTableColumn:item:),
1212  CPOutlineViewDelegate_outlineView_shouldTypeSelectForEvent_withCurrentSearchString_ , @selector(outlineView:shouldTypeSelectForEvent:withCurrentSearchString:),
1213  CPOutlineViewDelegate_outlineView_sizeToFitWidthOfColumn_ , @selector(outlineView:sizeToFitWidthOfColumn:),
1214  CPOutlineViewDelegate_outlineView_toolTipForView_rect_tableColumn_item_mouseLocation_, @selector(outlineView:toolTipForView:rect:tableColumn:item:mouseLocation:),
1215  CPOutlineViewDelegate_outlineView_typeSelectStringForTableColumn_item_ , @selector(outlineView:typeSelectStringForTableColumn:item:),
1216  CPOutlineViewDelegate_outlineView_willDisplayOutlineView_forTableColumn_item_ , @selector(outlineView:willDisplayOutlineView:forTableColumn:item:),
1217  CPOutlineViewDelegate_outlineView_willDisplayView_forTableColumn_item_ , @selector(outlineView:willDisplayView:forTableColumn:item:),
1218  CPOutlineViewDelegate_outlineView_willRemoveView_forTableColumn_item_ , @selector(outlineView:willRemoveView:forTableColumn:item:),
1219  CPOutlineViewDelegate_selectionShouldChangeInOutlineView_ , @selector(selectionShouldChangeInOutlineView:),
1220  CPOutlineViewDelegate_outlineView_menuForTableColumn_item_ , @selector(outlineView:menuForTableColumn:item:)
1221  ],
1222  delegateCount = [delegateMethods count];
1223 
1224  for (var i = 0; i < delegateCount; i += 2)
1225  {
1226  var bitMask = delegateMethods[i],
1227  selector = delegateMethods[i + 1];
1228 
1229  if ([_outlineViewDelegate respondsToSelector:selector])
1230  _implementedOutlineViewDelegateMethods |= bitMask;
1231  }
1232 
1233  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidMove:)])
1234  [defaultCenter
1235  addObserver:_outlineViewDelegate
1236  selector:@selector(outlineViewColumnDidMove:)
1238  object:self];
1239 
1240  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidResize:)])
1241  [defaultCenter
1242  addObserver:_outlineViewDelegate
1243  selector:@selector(outlineViewColumnDidMove:)
1245  object:self];
1246 
1247  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionDidChange:)])
1248  [defaultCenter
1249  addObserver:_outlineViewDelegate
1250  selector:@selector(outlineViewSelectionDidChange:)
1252  object:self];
1253 
1254  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionIsChanging:)])
1255  [defaultCenter
1256  addObserver:_outlineViewDelegate
1257  selector:@selector(outlineViewSelectionIsChanging:)
1259  object:self];
1260 
1261  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillExpand:)])
1262  [defaultCenter
1263  addObserver:_outlineViewDelegate
1264  selector:@selector(outlineViewItemWillExpand:)
1266  object:self];
1267 
1268  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidExpand:)])
1269  [defaultCenter
1270  addObserver:_outlineViewDelegate
1271  selector:@selector(outlineViewItemDidExpand:)
1273  object:self];
1274 
1275  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillCollapse:)])
1276  [defaultCenter
1277  addObserver:_outlineViewDelegate
1278  selector:@selector(outlineViewItemWillCollapse:)
1280  object:self];
1281 
1282  if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidCollapse:)])
1283  [defaultCenter
1284  addObserver:_outlineViewDelegate
1285  selector:@selector(outlineViewItemDidCollapse:)
1287  object:self];
1288 
1289  [self _updateIsViewBased];
1290 
1291  if ([self _delegateRespondsToDataViewForTableColumn])
1292  CPLog.warn("outlineView:dataViewForTableColumn:item: is deprecated. You should use -outlineView:viewForTableColumn:item: where you can request the view with -makeViewWithIdentifier:owner:");
1293 }
1294 
1299 {
1300  return _outlineViewDelegate;
1301 }
1302 
1310 - (void)setDisclosureControlPrototype:(CPControl)aControl
1311 {
1312  _disclosureControlPrototype = aControl;
1313  _disclosureControlData = nil;
1314  _disclosureControlQueue = [];
1315 
1316  // FIXME: really?
1317  [self reloadData];
1318 }
1319 
1323 - (void)_reloadDataViews
1324 {
1325  [self reloadItem:nil reloadChildren:YES];
1326 }
1327 
1338 - (void)addTableColumn:(CPTableColumn)aTableColumn
1339 {
1340  [super addTableColumn:aTableColumn];
1341 
1342  if ([self numberOfColumns] === 1)
1343  _outlineTableColumn = aTableColumn;
1344 }
1348 - (void)removeTableColumn:(CPTableColumn)aTableColumn
1349 {
1350  if (aTableColumn === [self outlineTableColumn])
1351  CPLog("CPOutlineView cannot remove outlineTableColumn with removeTableColumn:. User setOutlineTableColumn: instead.");
1352  else
1353  [super removeTableColumn:aTableColumn];
1354 }
1355 
1356 - (void)_addDraggedDataView:(CPView)aDataView toView:(CPView)aSuperview forColumn:(CPInteger)column row:(CPInteger)row offset:(CGPoint)offset
1357 {
1358  var control;
1359 
1360  [super _addDraggedDataView:aDataView toView:aSuperview forColumn:column row:row offset:offset];
1361 
1362  if (_tableColumns[column] === _outlineTableColumn && (control = _disclosureControlsForRows[row]))
1363  {
1364  var controlFrame = [self frameOfOutlineDisclosureControlAtRow:row];
1365 
1366  controlFrame.origin.x -= offset.x;
1367  controlFrame.origin.y -= offset.y;
1368 
1369  [control setFrame:controlFrame];
1370  [aSuperview addSubview:control];
1371  }
1372 }
1373 
1379 - (CGRect)frameOfDataViewAtColumn:(CPInteger)aColumn row:(CPInteger)aRow
1380 {
1381  var tableColumn = [self tableColumns][aColumn];
1382 
1383  if (tableColumn === _outlineTableColumn)
1384  return [self _frameOfOutlineDataViewAtRow:aRow];
1385 
1386  return [super frameOfDataViewAtColumn:aColumn row:aRow];
1387 }
1388 
1404 - (void)setDropItem:(id)theItem dropChildIndex:(int)theIndex
1405 {
1406  if (_dropItem !== theItem && theIndex < 0 && [self isExpandable:theItem] && ![self isItemExpanded:theItem])
1407  {
1408  if (_dragHoverTimer)
1409  [_dragHoverTimer invalidate];
1410 
1411  var autoExpandCallBack = function()
1412  {
1413  if (_dropItem)
1414  {
1415  [_dropOperationFeedbackView blink];
1417  }
1418  };
1419 
1420  _dragHoverTimer = [CPTimer scheduledTimerWithTimeInterval:.8 callback:autoExpandCallBack repeats:NO];
1421  }
1422 
1423  if (theIndex >= 0)
1424  {
1425  [_dragHoverTimer invalidate];
1426  _dragHoverTimer = nil;
1427  }
1428 
1429  _dropItem = theItem;
1430  _retargetedItem = theItem;
1431  _shouldRetargetItem = YES;
1432 
1433  _retargedChildIndex = theIndex;
1434  _shouldRetargetChildIndex = YES;
1435 
1436  // set CPTableView's _retargetedDropRow based on retargetedItem and retargetedChildIndex
1437  var retargetedItemInfo = (_retargetedItem !== nil) ? _itemInfosForItems[[_retargetedItem UID]] : _rootItemInfo;
1438 
1439  if (_retargedChildIndex === [retargetedItemInfo.children count])
1440  {
1441  var retargetedChildItem = [retargetedItemInfo.children lastObject];
1442  _retargetedDropRow = [self rowForItem:retargetedChildItem] + 1;
1443  }
1444  else
1445  {
1446  var retargetedChildItem = (_retargedChildIndex !== CPOutlineViewDropOnItemIndex) ? retargetedItemInfo.children[_retargedChildIndex] : _retargetedItem;
1447  _retargetedDropRow = [self rowForItem:retargetedChildItem];
1448  }
1449 }
1450 
1454 - (void)_draggingEnded
1455 {
1456  [super _draggingEnded];
1457  _dropItem = nil;
1458  [_dragHoverTimer invalidate];
1459  _dragHoverTimer = nil;
1460 }
1461 
1465 - (id)_parentItemForUpperRow:(CPInteger)theUpperRowIndex andLowerRow:(CPInteger)theLowerRowIndex atMouseOffset:(CGPoint)theOffset
1466 {
1467  if (_shouldRetargetItem)
1468  return _retargetedItem;
1469 
1470  var lowerLevel = [self levelForRow:theLowerRowIndex],
1471  upperItem = [self itemAtRow:theUpperRowIndex],
1472  upperLevel = [self levelForItem:upperItem];
1473 
1474  // If the row above us has a higher level the item can be added to multiple parent items
1475  // Determine which one by looping through all possible parents and return the first
1476  // of which the indentation level is larger than the current x offset
1477  while (upperLevel > lowerLevel)
1478  {
1479  upperLevel = [self levelForItem:upperItem];
1480 
1481  // See if this item's indentation level matches the mouse offset
1482  if (theOffset.x > (upperLevel + 1) * [self indentationPerLevel])
1483  return [self parentForItem:upperItem];
1484 
1485  // Check the next parent
1486  upperItem = [self parentForItem:upperItem];
1487  }
1488 
1489  return [self parentForItem:[self itemAtRow:theLowerRowIndex]];
1490 }
1491 
1495 - (CGRect)_rectForDropHighlightViewBetweenUpperRow:(CPInteger)theUpperRowIndex andLowerRow:(CPInteger)theLowerRowIndex offset:(CGPoint)theOffset
1496 {
1497  // Call super and the update x to reflect the current indentation level
1498  var rect = [super _rectForDropHighlightViewBetweenUpperRow:theUpperRowIndex andLowerRow:theLowerRowIndex offset:theOffset],
1499  parentItem = [self _parentItemForUpperRow:theUpperRowIndex andLowerRow:theLowerRowIndex atMouseOffset:theOffset],
1500  level = [self levelForItem:parentItem];
1501 
1502  rect.origin.x = (level + 1) * [self indentationPerLevel];
1503  rect.size.width -= rect.origin.x; // This assumes that the x returned by super is zero
1504 
1505  return rect;
1506 }
1507 
1512 - (void)_layoutViewsForRowIndexes:(CPIndexSet)rowIndexes columnIndexes:(CPIndexSet)columnIndexes
1513 {
1514  [self _enumerateViewsInRows:rowIndexes columns:columnIndexes usingBlock:function(view, row, column, stop)
1515  {
1516  var control;
1517 
1518  [view setFrame:[self frameOfDataViewAtColumn:column row:row]];
1519 
1520  if (_tableColumns[column] === _outlineTableColumn && (control = _disclosureControlsForRows[row]))
1521  {
1522  var frame = [self frameOfOutlineDisclosureControlAtRow:row];
1523  [control setFrame:frame];
1524  }
1525  }];
1526 
1527  [self setNeedsDisplay:YES];
1528 }
1529 
1533 - (void)_loadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
1534 {
1535  [super _loadDataViewsInRows:rows columns:columns];
1536 
1537  var outlineColumn = [[self tableColumns] indexOfObjectIdenticalTo:[self outlineTableColumn]];
1538 
1539  if (![columns containsIndex:outlineColumn] || outlineColumn === _draggedColumnIndex)
1540  return;
1541 
1542  var rowArray = [];
1543 
1544  [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
1545 
1546  var rowIndex = 0,
1547  rowsCount = rowArray.length;
1548 
1549  for (; rowIndex < rowsCount; ++rowIndex)
1550  {
1551  var row = rowArray[rowIndex],
1552  item = _itemsForRows[row],
1553  isExpandable = [self isExpandable:item];
1554 
1555  if (!isExpandable)
1556  continue;
1557 
1558  var disclosureControlFrame = [self frameOfOutlineDisclosureControlAtRow:row];
1559 
1560  if (CGRectIsEmpty(disclosureControlFrame))
1561  continue;
1562 
1563  var control = [self _dequeueDisclosureControl];
1564 
1565  _disclosureControlsForRows[row] = control;
1566 
1567  [control setState:[self isItemExpanded:item] ? CPOnState : CPOffState];
1568  var selector = [self isRowSelected:row] ? @"setThemeState:" : @"unsetThemeState:";
1569  [control performSelector:CPSelectorFromString(selector) withObject:CPThemeStateSelected];
1570  [control setFrame:disclosureControlFrame];
1571 
1572  [self addSubview:control];
1573  }
1574 }
1575 
1579 - (void)_unloadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
1580 {
1581  [super _unloadDataViewsInRows:rows columns:columns];
1582 
1583  var outlineColumn = [[self tableColumns] indexOfObjectIdenticalTo:[self outlineTableColumn]];
1584 
1585  if (![columns containsIndex:outlineColumn])
1586  return;
1587 
1588  var rowArray = [];
1589 
1590  [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
1591 
1592  var rowIndex = 0,
1593  rowsCount = rowArray.length;
1594 
1595  for (; rowIndex < rowsCount; ++rowIndex)
1596  {
1597  var row = rowArray[rowIndex],
1598  control = _disclosureControlsForRows[row];
1599 
1600  if (!control)
1601  continue;
1602 
1603  [control removeFromSuperview];
1604 
1605  [self _enqueueDisclosureControl:control];
1606 
1607  _disclosureControlsForRows[row] = nil;
1608  }
1609 }
1610 
1614 - (void)_toggleFromDisclosureControl:(CPControl)aControl
1615 {
1616  var controlFrame = [aControl frame],
1617  item = [self itemAtRow:[self rowAtPoint:CGPointMake(CGRectGetMinX(controlFrame), CGRectGetMidY(controlFrame))]];
1618 
1619  if ([self isItemExpanded:item])
1620  [self collapseItem:item];
1621 
1622  else
1623  [self expandItem:item expandChildren:([[CPApp currentEvent] modifierFlags] & CPAlternateKeyMask)];
1624 }
1625 
1629 - (void)_enqueueDisclosureControl:(CPControl)aControl
1630 {
1631  _disclosureControlQueue.push(aControl);
1632 }
1633 
1637 - (CPControl)_dequeueDisclosureControl
1638 {
1639  if (_disclosureControlQueue.length)
1640  return _disclosureControlQueue.pop();
1641 
1642  if (!_disclosureControlData)
1643  if (!_disclosureControlPrototype)
1644  return nil;
1645  else
1646  _disclosureControlData = [CPKeyedArchiver archivedDataWithRootObject:_disclosureControlPrototype];
1647 
1648  var disclosureControl = [CPKeyedUnarchiver unarchiveObjectWithData:_disclosureControlData];
1649 
1650  [disclosureControl setTarget:self];
1651  [disclosureControl setAction:@selector(_toggleFromDisclosureControl:)];
1652 
1653  return disclosureControl;
1654 }
1655 
1659 - (void)_noteSelectionIsChanging
1660 {
1661  if (!_coalesceSelectionNotificationState || _coalesceSelectionNotificationState === CPOutlineViewCoalesceSelectionNotificationStateOn)
1662  {
1664  postNotificationName:CPOutlineViewSelectionIsChangingNotification
1665  object:self
1666  userInfo:nil];
1667  }
1668 
1669  if (_coalesceSelectionNotificationState === CPOutlineViewCoalesceSelectionNotificationStateOn)
1670  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateDid;
1671 }
1672 
1676 - (void)_noteSelectionDidChange
1677 {
1678  if (!_coalesceSelectionNotificationState)
1679  {
1681  postNotificationName:CPOutlineViewSelectionDidChangeNotification
1682  object:self
1683  userInfo:nil];
1684  }
1685 
1686  if (_coalesceSelectionNotificationState === CPOutlineViewCoalesceSelectionNotificationStateOn)
1687  _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateDid;
1688 }
1689 
1693 - (void)_noteItemWillExpand:(id)item
1694 {
1696  postNotificationName:CPOutlineViewItemWillExpandNotification
1697  object:self
1698  userInfo:@{ "CPObject": item }];
1699 }
1700 
1704 - (void)_noteItemDidExpand:(id)item
1705 {
1707  postNotificationName:CPOutlineViewItemDidExpandNotification
1708  object:self
1709  userInfo:@{ "CPObject": item }];
1710 }
1711 
1715 - (void)_noteItemWillCollapse:(id)item
1716 {
1718  postNotificationName:CPOutlineViewItemWillCollapseNotification
1719  object:self
1720  userInfo:@{ "CPObject": item }];
1721 }
1722 
1726 - (void)_noteItemDidCollapse:(id)item
1727 {
1729  postNotificationName:CPOutlineViewItemDidCollapseNotification
1730  object:self
1731  userInfo:@{ "CPObject": item }];
1732 }
1733 
1734 - (void)keyDown:(CPEvent)anEvent
1735 {
1736  var character = [anEvent charactersIgnoringModifiers],
1737  modifierFlags = [anEvent modifierFlags];
1738 
1739  // Check for the key events manually, as opposed to waiting for CPWindow to sent the actual action message
1740  // in _processKeyboardUIKey:, because we might not want to handle the arrow events.
1741 
1742  if (character !== CPRightArrowFunctionKey && character !== CPLeftArrowFunctionKey)
1743  return [super keyDown:anEvent];
1744 
1745  var rows = [self selectedRowIndexes],
1746  indexes = [],
1747  items = [];
1748 
1749  [rows getIndexes:indexes maxCount:-1 inIndexRange:nil];
1750 
1751  var i = 0,
1752  c = [indexes count];
1753 
1754  for (; i < c; i++)
1755  items.push([self itemAtRow:indexes[i]]);
1756 
1757  if (character === CPRightArrowFunctionKey)
1758  {
1759  for (var i = 0; i < c; i++)
1760  [self expandItem:items[i]];
1761  }
1762  else if (character === CPLeftArrowFunctionKey)
1763  {
1764  // When a single, collapsed item is selected and the left arrow key is pressed, the parent
1765  // should be selected if possible.
1766  if (c == 1)
1767  {
1768  var theItem = items[0];
1769  if (![self isItemExpanded:theItem])
1770  {
1771  var parent = [self parentForItem:theItem],
1772  shouldSelect = parent && SELECTION_SHOULD_CHANGE(self) && SHOULD_SELECT_ITEM(self, parent);
1773  if (shouldSelect)
1774  {
1775  var rowIndex = [self rowForItem:parent];
1777  [self scrollRowToVisible:rowIndex];
1778  return;
1779  }
1780  }
1781  }
1782 
1783  for (var i = 0; i < c; i++)
1784  [self collapseItem:items[i]];
1785  }
1786 
1787  [super keyDown:anEvent];
1788 }
1789 
1790 - (BOOL)_sendDelegateDeleteKeyPressed
1791 {
1792  if ([[self delegate] respondsToSelector: @selector(outlineViewDeleteKeyPressed:)])
1793  {
1794  [[self delegate] outlineViewDeleteKeyPressed:self];
1795  return YES;
1796  }
1797 
1798  return NO;
1799 }
1800 
1801 - (BOOL)_sendDelegateShouldShowOutlineDisclosureControlForItem:(id)anItem
1802 {
1803  if (!(_implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldShowOutlineDisclosureControlForItem_))
1804  return YES;
1805 
1806  return [_outlineViewDelegate outlineView:self shouldShowOutlineDisclosureControlForItem:anItem];
1807 }
1808 
1809 - (BOOL)_sendDataSourceShouldDeferDisplayingChildrenOfItem:(id)anItem
1810 {
1811  if (!(_implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_shouldDeferDisplayingChildrenOfItem_))
1812  return YES;
1813 
1814  return [_outlineViewDataSource outlineView:self shouldDeferDisplayingChildrenOfItem:anItem];
1815 }
1816 
1817 - (CPView)_sendDelegateViewForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
1818 {
1819  return [_outlineViewDelegate outlineView:self viewForTableColumn:aTableColumn item:[self itemAtRow:aRow]];
1820 }
1821 
1822 - (CPView)_sendDelegateDataViewForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
1823 {
1824  return [_outlineViewDelegate outlineView:self dataViewForTableColumn:aTableColumn item:[self itemAtRow:aRow]];
1825 }
1826 
1827 - (BOOL)_dataSourceRespondsToObjectValueForTableColumn
1828 {
1829  return _implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_objectValue_forTableColumn_byItem_;
1830 }
1831 
1832 - (BOOL)_delegateRespondsToViewForTableColumn
1833 {
1834  return _implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_viewForTableColumn_item_;
1835 }
1836 
1837 - (BOOL)_delegateRespondsToDataViewForTableColumn
1838 {
1839  return _implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_dataViewForTableColumn_item_;
1840 }
1841 
1842 - (id)_hitTest:(CPView)aView
1843 {
1844  if ([aView isKindOfClass:[CPDisclosureButton class]])
1845  return aView;
1846 
1847  return [super _hitTest:aView];
1848 }
1849 
1850 - (BOOL)_delegateRespondsToShouldExpandItem
1851 {
1852  return _implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldExpandItem_;
1853 }
1854 
1855 - (BOOL)_delegateRespondsToShouldCollapseItem
1856 {
1857  return _implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldCollapseItem_;
1858 }
1859 
1864 - (BOOL)_delegateRespondsToSelectionIndexesForProposedSelection
1865 {
1866  return _implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_selectionIndexesForProposedSelection_;
1867 }
1868 
1873 - (BOOL)_delegateRespondsToShouldSelectRow
1874 {
1875  return _implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectItem_;
1876 }
1877 
1878 @end
1879 
1880 @implementation _CPOutlineViewTableViewDataSource : CPObject
1881 {
1882  CPObject _outlineView;
1883 }
1884 
1885 - (id)initWithOutlineView:(CPOutlineView)anOutlineView
1886 {
1887  self = [super init];
1888 
1889  if (self)
1890  _outlineView = anOutlineView;
1891 
1892  return self;
1893 }
1894 
1895 - (CPInteger)numberOfRowsInTableView:(CPTableView)anOutlineView
1896 {
1897  return _outlineView._itemsForRows.length;
1898 }
1899 
1900 - (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
1901 {
1902  return [_outlineView._outlineViewDataSource outlineView:_outlineView objectValueForTableColumn:aTableColumn byItem:_outlineView._itemsForRows[aRow]];
1903 }
1904 
1905 - (void)tableView:(CPTableView)aTableView setObjectValue:(id)aValue forTableColumn:(CPTableColumn)aColumn row:(CPInteger)aRow
1906 {
1907  if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_setObjectValue_forTableColumn_byItem_))
1908  return;
1909  [_outlineView._outlineViewDataSource outlineView:_outlineView setObjectValue:aValue forTableColumn:aColumn byItem:_outlineView._itemsForRows[aRow]];
1910 }
1911 
1912 - (BOOL)tableView:(CPTableView)aTableColumn writeRowsWithIndexes:(CPIndexSet)theIndexes toPasteboard:(CPPasteboard)thePasteboard
1913 {
1914  if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_writeItems_toPasteboard_))
1915  return NO;
1916 
1917  var items = [],
1918  index = [theIndexes firstIndex];
1919 
1920  while (index !== CPNotFound)
1921  {
1922  [items addObject:[_outlineView itemAtRow:index]]
1923  index = [theIndexes indexGreaterThanIndex:index];
1924  }
1925 
1926  return [_outlineView._outlineViewDataSource outlineView:_outlineView writeItems:items toPasteboard:thePasteboard];
1927 }
1928 
1929 - (int)_childIndexForDropOperation:(CPTableViewDropOperation)theDropOperation row:(CPInteger)theRow offset:(CGPoint)theOffset
1930 {
1931  if (_outlineView._shouldRetargetChildIndex)
1932  return _outlineView._retargedChildIndex;
1933 
1934  var childIndex = CPNotFound;
1935 
1936  if (theDropOperation === CPTableViewDropAbove)
1937  {
1938  var parentItem = [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset],
1939  itemInfo = (parentItem !== nil) ? _outlineView._itemInfosForItems[[parentItem UID]] : _outlineView._rootItemInfo,
1940  children = itemInfo.children;
1941 
1942  childIndex = [children indexOfObject:[_outlineView itemAtRow:theRow]];
1943 
1944  if (childIndex === CPNotFound)
1945  childIndex = children.length;
1946  }
1947  else if (theDropOperation === CPTableViewDropOn)
1948  childIndex = -1;
1949 
1950  return childIndex;
1951 }
1952 
1953 - (void)_parentItemForDropOperation:(CPTableViewDropOperation)theDropOperation row:(CPInteger)theRow offset:(CGPoint)theOffset
1954 {
1955  if (theDropOperation === CPTableViewDropAbove)
1956  return [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset]
1957 
1958  return [_outlineView itemAtRow:theRow];
1959 }
1960 
1961 - (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(id /*< CPDraggingInfo >*/)theInfo
1962  proposedRow:(CPInteger)theRow proposedDropOperation:(CPTableViewDropOperation)theOperation
1963 {
1964  if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_validateDrop_proposedItem_proposedChildIndex_))
1965  return CPDragOperationNone;
1966 
1967  // Make sure the retargeted item and index are reset
1968  _outlineView._retargetedItem = nil;
1969  _outlineView._shouldRetargetItem = NO;
1970 
1971  _outlineView._retargedChildIndex = nil;
1972  _outlineView._shouldRetargetChildIndex = NO;
1973 
1974  var location = [_outlineView convertPoint:[theInfo draggingLocation] fromView:nil],
1975  parentItem = [self _parentItemForDropOperation:theOperation row:theRow offset:location],
1976  childIndex = [self _childIndexForDropOperation:theOperation row:theRow offset:location];
1977 
1978  return [_outlineView._outlineViewDataSource outlineView:_outlineView validateDrop:theInfo proposedItem:parentItem proposedChildIndex:childIndex];
1979 }
1980 
1981 - (BOOL)tableView:(CPTableView)aTableView acceptDrop:(id /*<CPDraggingInfo>*/)theInfo row:(CPInteger)theRow dropOperation:(CPTableViewDropOperation)theOperation
1982 {
1983  if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_acceptDrop_item_childIndex_))
1984  return NO;
1985 
1986  var location = [_outlineView convertPoint:[theInfo draggingLocation] fromView:nil],
1987  parentItem = [self _parentItemForDropOperation:theOperation row:theRow offset:location],
1988  childIndex = [self _childIndexForDropOperation:theOperation row:theRow offset:location];
1989 
1990  _outlineView._retargetedItem = nil;
1991  _outlineView._shouldRetargetItem = NO;
1992 
1993  _outlineView._retargedChildIndex = nil;
1994  _outlineView._shouldRetargetChildIndex = NO;
1995 
1996  return [_outlineView._outlineViewDataSource outlineView:_outlineView acceptDrop:theInfo item:parentItem childIndex:childIndex];
1997 }
1998 
1999 - (void)tableView:(CPTableView)aTableView sortDescriptorsDidChange:(CPArray)oldSortDescriptors
2000 {
2001  if ((_outlineView._implementedOutlineViewDataSourceMethods &
2003  {
2004  [[_outlineView dataSource] outlineView:_outlineView sortDescriptorsDidChange:oldSortDescriptors];
2005  }
2006 }
2007 
2008 @end
2009 
2010 @implementation _CPOutlineViewTableViewDelegate : CPObject
2011 {
2012  CPOutlineView _outlineView;
2013 }
2014 
2015 - (id)initWithOutlineView:(CPOutlineView)anOutlineView
2016 {
2017  self = [super init];
2018 
2019  if (self)
2020  _outlineView = anOutlineView;
2021 
2022  return self;
2023 }
2024 
2025 - (BOOL)tableView:(CPTableView)theTableView shouldSelectRow:(CPInteger)theRow
2026 {
2027  return SHOULD_SELECT_ITEM(_outlineView, [_outlineView itemAtRow:theRow]);
2028 }
2029 
2030 - (BOOL)selectionShouldChangeInTableView:(CPTableView)theTableView
2031 {
2032  return SELECTION_SHOULD_CHANGE(_outlineView);
2033 }
2034 
2035 - (BOOL)tableView:(CPTableView)aTableView shouldEditTableColumn:(CPTableColumn)aColumn row:(CPInteger)aRow
2036 {
2037  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldEditTableColumn_item_))
2038  return [_outlineView._outlineViewDelegate outlineView:_outlineView shouldEditTableColumn:aColumn item:[_outlineView itemAtRow:aRow]];
2039 
2040  return NO;
2041 }
2042 
2043 - (float)tableView:(CPTableView)theTableView heightOfRow:(CPInteger)theRow
2044 {
2045  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_heightOfRowByItem_))
2046  return [_outlineView._outlineViewDelegate outlineView:_outlineView heightOfRowByItem:[_outlineView itemAtRow:theRow]];
2047 
2048  return [theTableView rowHeight];
2049 }
2050 
2051 - (void)tableView:(CPTableView)aTableView willDisplayView:(id)aView forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
2052 {
2053  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_willDisplayView_forTableColumn_item_))
2054  {
2055  var item = [_outlineView itemAtRow:aRowIndex];
2056  [_outlineView._outlineViewDelegate outlineView:_outlineView willDisplayView:aView forTableColumn:aTableColumn item:item];
2057  }
2058 }
2059 
2060 - (void)tableView:(CPTableView)aTableView willRemoveView:(id)aView forTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRowIndex
2061 {
2062  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_willRemoveView_forTableColumn_item_))
2063  {
2064  var item = [_outlineView itemAtRow:aRowIndex];
2065  [_outlineView._outlineViewDelegate outlineView:_outlineView willRemoveView:aView forTableColumn:aTableColumn item:item];
2066  }
2067 }
2068 
2069 - (BOOL)tableView:(CPTableView)aTableView isGroupRow:(CPInteger)aRow
2070 {
2071  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_isGroupItem_))
2072  return [_outlineView._outlineViewDelegate outlineView:_outlineView isGroupItem:[_outlineView itemAtRow:aRow]];
2073 
2074  return NO;
2075 }
2076 
2077 - (CPMenu)tableView:(CPTableView)aTableView menuForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
2078 {
2079  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_menuForTableColumn_item_))
2080  {
2081  var item = [_outlineView itemAtRow:aRow];
2082  return [_outlineView._outlineViewDelegate outlineView:_outlineView menuForTableColumn:aTableColumn item:item]
2083  }
2084 
2085  // We reimplement CPView menuForEvent: because we can't call it directly. CPTableView implements menuForEvent:
2086  // to call this delegate method.
2087  return [_outlineView menu] || [[_outlineView class] defaultMenu];
2088 }
2089 
2090 - (CPIndexSet)tableView:(CPTableView)aTableView selectionIndexesForProposedSelection:(CPIndexSet)anIndexSet
2091 {
2092  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_selectionIndexesForProposedSelection_))
2093  return [_outlineView._outlineViewDelegate outlineView:_outlineView selectionIndexesForProposedSelection:anIndexSet];
2094 
2095  return anIndexSet;
2096 }
2097 
2098 - (BOOL)tableView:(CPTableView)aTableView shouldSelectTableColumn:(CPTableColumn)aTableColumn
2099 {
2100  if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectTableColumn_))
2101  return [_outlineView._outlineViewDelegate outlineView:_outlineView shouldSelectTableColumn:aTableColumn];
2102 
2103  return YES;
2104 }
2105 
2106 @end
2107 
2108 @implementation CPDisclosureButton : CPButton
2109 {
2110  float _angle;
2111 }
2112 
2113 - (id)initWithFrame:(CGRect)aFrame
2114 {
2115  self = [super initWithFrame:aFrame];
2116 
2117  if (self)
2118  [self setBordered:NO];
2119 
2120  return self;
2121 }
2122 
2123 - (void)setState:(CPInteger)aState
2124 {
2125  [super setState:aState];
2126 
2127  if ([self state] === CPOnState)
2128  _angle = 0.0;
2129 
2130  else
2131  _angle = -PI_2;
2132 }
2133 
2134 - (void)drawRect:(CGRect)aRect
2135 {
2136  var bounds = [self bounds],
2138  width = CGRectGetWidth(bounds),
2139  height = CGRectGetHeight(bounds);
2140 
2141  CGContextBeginPath(context);
2142 
2143  if (_angle)
2144  {
2145  var centre = CGPointMake(FLOOR(width / 2.0), FLOOR(height / 2.0));
2146  CGContextTranslateCTM(context, centre.x, centre.y);
2147  CGContextRotateCTM(context, _angle);
2148  CGContextTranslateCTM(context, -centre.x, -centre.y);
2149  }
2150 
2151  // Center, but crisp.
2152  CGContextTranslateCTM(context, FLOOR((width - 9.0) / 2.0), FLOOR((height - 8.0) / 2.0));
2153 
2154  CGContextMoveToPoint(context, 0.0, 0.0);
2155  CGContextAddLineToPoint(context, 9.0, 0.0);
2156  CGContextAddLineToPoint(context, 4.5, 8.0);
2157  CGContextClosePath(context);
2158 
2159  CGContextSetFillColor(context,
2160  colorForDisclosureTriangle([self hasThemeState:CPThemeStateSelected],
2161  [self hasThemeState:CPThemeStateHighlighted]));
2162  CGContextFillPath(context);
2163 
2164  CGContextBeginPath(context);
2165  CGContextMoveToPoint(context, 0.0, 0.0);
2166  CGContextAddLineToPoint(context, 4.5, 8.0);
2167 
2168  if (_angle === 0.0)
2169  CGContextAddLineToPoint(context, 9.0, 0.0);
2170 
2171  CGContextSetStrokeColor(context, [CPColor colorWithCalibratedWhite:1.0 alpha: 0.7]);
2172  CGContextStrokePath(context);
2173 }
2174 
2175 @end
2176 
2177 
2178 var CPOutlineViewIndentationPerLevelKey = @"CPOutlineViewIndentationPerLevelKey",
2179  CPOutlineViewOutlineTableColumnKey = @"CPOutlineViewOutlineTableColumnKey",
2180  CPOutlineViewDataSourceKey = @"CPOutlineViewDataSourceKey",
2181  CPOutlineViewDelegateKey = @"CPOutlineViewDelegateKey";
2182 
2184 
2185 - (id)initWithCoder:(CPCoder)aCoder
2186 {
2187  self = [super initWithCoder:aCoder];
2188 
2189  if (self)
2190  {
2191  // The root item has weight "0", thus represents the weight solely of its descendants.
2192  _rootItemInfo = { isExpanded:YES, isExpandable:NO, level:-1, row:-1, children:[], weight:0 };
2193 
2194  _itemsForRows = [];
2195  _itemInfosForItems = { };
2196  _disclosureControlsForRows = [];
2197 
2199  [self setDisclosureControlPrototype:[[CPDisclosureButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 10.0)]];
2200 
2201  _outlineTableColumn = [aCoder decodeObjectForKey:CPOutlineViewOutlineTableColumnKey];
2202  _indentationPerLevel = [aCoder decodeFloatForKey:CPOutlineViewIndentationPerLevelKey];
2203 
2204  _outlineViewDataSource = [aCoder decodeObjectForKey:CPOutlineViewDataSourceKey];
2205  _outlineViewDelegate = [aCoder decodeObjectForKey:CPOutlineViewDelegateKey];
2206 
2207  [super setDataSource:[[_CPOutlineViewTableViewDataSource alloc] initWithOutlineView:self]];
2208  [super setDelegate:[[_CPOutlineViewTableViewDelegate alloc] initWithOutlineView:self]];
2209 
2210  [self _updateIsViewBased];
2211  }
2212 
2213  return self;
2214 }
2215 
2216 - (void)encodeWithCoder:(CPCoder)aCoder
2217 {
2218  // Make sure we don't encode our internal delegate and data source.
2219  var internalDelegate = _delegate,
2220  internalDataSource = _dataSource;
2221  _delegate = nil;
2222  _dataSource = nil;
2223  [super encodeWithCoder:aCoder];
2224  _delegate = internalDelegate;
2225  _dataSource = internalDataSource;
2226 
2227  [aCoder encodeObject:_outlineTableColumn forKey:CPOutlineViewOutlineTableColumnKey];
2228  [aCoder encodeFloat:_indentationPerLevel forKey:CPOutlineViewIndentationPerLevelKey];
2229 
2230  [aCoder encodeObject:_outlineViewDataSource forKey:CPOutlineViewDataSourceKey];
2231  [aCoder encodeObject:_outlineViewDelegate forKey:CPOutlineViewDelegateKey];
2232 }
2233 
2234 @end
2235 
2236 
2237 var colorForDisclosureTriangle = function(isSelected, isHighlighted)
2238 {
2239  return isSelected
2240  ? (isHighlighted
2242  : [CPColor colorWithCalibratedWhite:1.0 alpha: 1.0])
2243  : (isHighlighted
2244  ? [CPColor colorWithCalibratedWhite:0.4 alpha: 1.0]
2245  : [CPColor colorWithCalibratedWhite:0.5 alpha: 1.0]);
2246 };
int numberOfRows()
Definition: CPTableView.j:1609
CPInteger levelForItem:(id anItem)
var CPOutlineViewDataSource_outlineView_validateDrop_proposedRow_proposedDropOperation_
Definition: CPOutlineView.j:41
CPInteger rowForItem:(id anItem)
Used to implement exception handling (creating & raising).
Definition: CPException.h:2
var CPOutlineViewDelegate_outlineView_willRemoveView_forTableColumn_item_
Definition: CPOutlineView.j:74
function CGContextTranslateCTM(aContext, tx, ty)
Definition: CGContext.j:472
CPThemeStateSelected
Definition: CPTheme.j:548
BOOL isHighlighted()
Definition: CPControl.j:1001
Definition: CPMenu.h:2
CPIndexSet selectedRowIndexes()
Definition: CPTableView.j:1490
void addSubview:(CPView aSubview)
Definition: CPView.j:512
A CALayer is similar to a CPView
Definition: CALayer.j:46
var CPOutlineViewDelegate_outlineView_shouldTrackView_forTableColumn_item_
Definition: CPOutlineView.j:67
var CPOutlineViewDelegateKey
var CPOutlineViewDelegate_outlineView_isGroupItem_
Definition: CPOutlineView.j:55
CGRect frameOfDataViewAtColumn:row:(CPInteger aColumn, [row] CPInteger aRow)
Definition: CPTableView.j:2139
CPOutlineViewDropOnItemIndex
Definition: CPOutlineView.j:79
CPGraphicsContext currentContext()
void postNotificationName:object:userInfo:(CPString aNotificationName, [object] id anObject, [userInfo] CPDictionary aUserInfo)
int columnIndex
Definition: CPTableView.j:6308
id initWithCoder:(CPCoder aCoder)
Definition: CPTableView.j:6094
CPDragOperation CPDragOperationNone
var CPOutlineViewDelegate_outlineView_typeSelectStringForTableColumn_item_
Definition: CPOutlineView.j:71
var CPOutlineViewDelegate_outlineView_nextTypeSelectMatchFromItem_toItem_forString_
Definition: CPOutlineView.j:57
void setBordered:(BOOL shouldBeBordered)
Definition: CPButton.j:768
CPOutlineViewColumnDidResizeNotification
Definition: CPOutlineView.j:27
CPRightArrowFunctionKey
void reloadData()
Definition: CPTableView.j:599
var CPOutlineViewDataSource_outlineView_validateDrop_proposedItem_proposedChildIndex_
Definition: CPOutlineView.j:40
var CPOutlineViewDataSource_outlineView_namesOfPromisedFilesDroppedAtDestination_forDraggedItems_
Definition: CPOutlineView.j:42
CPOutlineViewItemDidExpandNotification
Definition: CPOutlineView.j:29
CGRect frameOfOutlineDisclosureControlAtRow:(CPInteger aRow)
function CGContextSetStrokeColor(aContext, aColor)
Definition: CGContext.j:675
CPInteger levelForRow:(CPInteger aRow)
CPArray tableColumns()
Definition: CPTableView.j:1244
CPOutlineViewSelectionIsChangingNotification
Definition: CPOutlineView.j:33
var CPOutlineViewDelegate_outlineView_shouldTypeSelectForEvent_withCurrentSearchString_
Definition: CPOutlineView.j:68
id initWithFrame:(CGRect aFrame)
Definition: CPButton.j:161
CGRect frameOfDataViewAtColumn:row:(CPInteger aColumn, [row] CPInteger aRow)
int width
A Cappuccino wrapper for any data type.
Definition: CPData.h:2
CGRect bounds()
Definition: CPView.j:1302
var CPOutlineViewDataSource_outlineView_sortDescriptorsDidChange_
Definition: CPOutlineView.j:49
A collection of unique integers.
Definition: CPIndexSet.h:2
void collapseItem:(id anItem)
CPString charactersIgnoringModifiers()
Definition: CPEvent.j:392
var CPOutlineViewDelegate_outlineView_shouldCollapseItem_
Definition: CPOutlineView.j:59
var CPOutlineViewDelegate_outlineView_shouldSelectItem_
Definition: CPOutlineView.j:63
CPTimer scheduledTimerWithTimeInterval:callback:repeats:(CPTimeInterval seconds, [callback] Function aFunction, [repeats] BOOL shouldRepeat)
Definition: CPTimer.j:70
Unarchives objects created using CPKeyedArchiver.
unsigned modifierFlags()
Definition: CPEvent.j:309
function CGContextAddLineToPoint(aContext, x, y)
Definition: CGContext.j:247
function CGContextStrokePath(aContext)
Definition: CGContext.j:619
void reloadItem:reloadChildren:(id anItem, [reloadChildren] BOOL shouldReloadChildren)
CPNotificationCenter defaultCenter()
void removeTableColumn:(CPTableColumn aTableColumn)
Definition: CPTableView.j:1106
var CPOutlineViewDataSource_outlineView_persistentObjectForItem_
Definition: CPOutlineView.j:45
function CGContextClosePath(aContext)
Definition: CGContext.j:322
var CPOutlineViewDelegate_outlineView_shouldShowOutlineDisclosureControlForItem_
Definition: CPOutlineView.j:65
void selectRowIndexes:byExtendingSelection:(CPIndexSet rows, [byExtendingSelection] BOOL shouldExtendSelection)
Definition: CPTableView.j:1365
var CPOutlineViewCoalesceSelectionNotificationStateOff
Definition: CPOutlineView.j:81
void setNeedsDisplay:(BOOL aFlag)
Definition: CPTableView.j:3957
var CPOutlineViewDataSource_outlineView_itemForPersistentObject_
Definition: CPOutlineView.j:44
void addTableColumn:(CPTableColumn aTableColumn)
Definition: CPTableView.j:1077
var CPOutlineViewOutlineTableColumnKey
var CPOutlineViewDelegate_outlineView_toolTipForView_rect_tableColumn_item_mouseLocation_
Definition: CPOutlineView.j:70
var CPOutlineViewDelegate_outlineView_didDragTableColumn_
Definition: CPOutlineView.j:53
var CPOutlineViewIndentationPerLevelKey
Implements keyed archiving of object graphs (e.g. for storing data).
var CPOutlineViewDelegate_outlineView_shouldExpandItem_
Definition: CPOutlineView.j:61
var CPOutlineViewDelegate_selectionShouldChangeInOutlineView_
Definition: CPOutlineView.j:75
An immutable string (collection of characters).
Definition: CPString.h:2
BOOL isRowSelected:(CPInteger aRow)
Definition: CPTableView.j:1554
var CPOutlineViewDataSource_outlineView_writeItems_toPasteboard_
Definition: CPOutlineView.j:47
if(CPFeatureIsCompatible(CPHTMLCanvasFeature))
void setState:(CPInteger aState)
Definition: CPButton.j:317
var CPOutlineViewDelegate_outlineView_heightOfRowByItem_
Definition: CPOutlineView.j:54
id init()
Definition: CPView.j:325
CPColor colorWithCalibratedWhite:alpha:(float white, [alpha] float alpha)
Definition: CPColor.j:172
function CGContextSetFillColor(aContext, aColor)
Definition: CGContext.j:663
function CGContextRotateCTM(aContext, anAngle)
Definition: CGContext.j:444
var CPOutlineViewDelegate_outlineView_mouseDownInHeaderOfTableColumn_
Definition: CPOutlineView.j:56
var CPOutlineViewDelegate_outlineView_shouldReorderColumn_toColumn_
Definition: CPOutlineView.j:62
CPTableView tableView
Definition: CPTableView.j:6194
CPTableViewDropAbove
Definition: CPTableView.j:77
CPTableViewDropOn
Definition: CPTableView.j:76
CPInteger rowAtPoint:(CGPoint aPoint)
Definition: CPTableView.j:1995
CPLeftArrowFunctionKey
function CGContextBeginPath(aContext)
Definition: CGContext.j:311
void setDelegate:(id< CPTableViewDelegate > aDelegate)
Definition: CPTableView.j:2793
var CPOutlineViewDelegate_outlineView_menuForTableColumn_item_
Definition: CPOutlineView.j:76
id initWithFrame:(CGRect aFrame)
Definition: CPTableView.j:352
BOOL isItemExpanded:(id anItem)
id parentForItem:(id anItem)
CPOutlineViewItemWillCollapseNotification
Definition: CPOutlineView.j:30
var CPOutlineViewDataSource_outlineView_objectValue_forTableColumn_byItem_
Definition: CPOutlineView.j:35
CPInternalInconsistencyException
Definition: CPException.j:28
var CPOutlineViewDataSourceKey
CPString name
Definition: CPException.j:47
var CPOutlineViewDelegate_outlineView_sizeToFitWidthOfColumn_
Definition: CPOutlineView.j:69
var CPOutlineViewDelegate_outlineView_shouldShowViewExpansionForTableColumn_item_
Definition: CPOutlineView.j:66
void setIndentationPerLevel:(float anIndentationWidth)
var CPOutlineViewDelegate_outlineView_shouldEditTableColumn_item_
Definition: CPOutlineView.j:60
A timer object that can send a message after the given time interval.
Definition: CPTimer.h:2
var CPOutlineViewCoalesceSelectionNotificationStateDid
Definition: CPOutlineView.j:83
Defines methods for use when archiving & restoring (enc/decoding).
Definition: CPCoder.h:2
var CPOutlineViewDelegate_outlineView_viewForTableColumn_item_
Definition: CPOutlineView.j:77
CPThemeStateHighlighted
Definition: CPTheme.j:547
BOOL indentationMarkerFollowsDataView()
void expandItem:expandChildren:(id anItem, [expandChildren] BOOL shouldExpandChildren)
var CPOutlineViewDelegate_outlineView_dataViewForTableColumn_item_
Definition: CPOutlineView.j:51
CPNotFound
Definition: CPObjJRuntime.j:62
id unarchiveObjectWithData:(CPData aData)
CPOutlineViewSelectionDidChangeNotification
Definition: CPOutlineView.j:32
global CPApp CPOutlineViewColumnDidMoveNotification
Definition: CPOutlineView.j:26
Sends messages (CPNotification) between objects.
void keyDown:(CPEvent anEvent)
Definition: CPTableView.j:5349
id new()
Definition: CPObject.j:122
var CPOutlineViewCoalesceSelectionNotificationStateOn
Definition: CPOutlineView.j:82
var CPOutlineViewDelegate_outlineView_shouldSelectTableColumn_
Definition: CPOutlineView.j:64
var CPOutlineViewDelegate_outlineView_willDisplayOutlineView_forTableColumn_item_
Definition: CPOutlineView.j:72
var CPOutlineViewDataSource_outlineView_setObjectValue_forTableColumn_byItem_
Definition: CPOutlineView.j:36
BOOL isExpandable:(id anItem)
var CPOutlineViewDelegate_outlineView_willDisplayView_forTableColumn_item_
Definition: CPOutlineView.j:73
#define SELECTION_SHOULD_CHANGE(anOutlineView)
Definition: CPOutlineView.j:85
CPOutlineViewItemWillExpandNotification
Definition: CPOutlineView.j:31
Definition: CPEvent.h:2
var CPOutlineViewDataSource_outlineView_acceptDrop_item_childIndex_
Definition: CPOutlineView.j:39
CPTableViewSelectionHighlightStyleSourceList
Definition: CPTableView.j:87
void encodeWithCoder:(CPCoder aCoder)
Definition: CPTableView.j:6148
#define SHOULD_SELECT_ITEM(anOutlineView, anItem)
Definition: CPOutlineView.j:87
CGRect frame()
Definition: CPView.j:1022
CPOutlineViewItemDidCollapseNotification
Definition: CPOutlineView.j:28
var CPOutlineViewDelegate_outlineView_didClickTableColumn_
Definition: CPOutlineView.j:52
void scrollRowToVisible:(int rowIndex)
Definition: CPTableView.j:2497
void setIndentationMarkerFollowsDataView:(BOOL indentationMarkerShouldFollowDataView)
var colorForDisclosureTriangle
CPOnState
Definition: CPControl.j:77
void setDisclosureControlPrototype:(CPControl aControl)
function CGContextFillPath(aContext)
Definition: CGContext.j:548
Definition: CPURL.h:2
void setDataSource:(id< CPTableViewDataSource > aDataSource)
Definition: CPTableView.j:547
id indexSetWithIndex:(int anIndex)
Definition: CPIndexSet.j:51
var CPOutlineViewDataSource_outlineView_shouldDeferDisplayingChildrenOfItem_
Definition: CPOutlineView.j:37
function CGContextMoveToPoint(aContext, x, y)
Definition: CGContext.j:344
float indentationPerLevel()
void expandItem:(id anItem)
CPRange function CPMakeRange(location, length)
Definition: CPRange.j:37
var CPOutlineViewDelegate_outlineView_selectionIndexesForProposedSelection_
Definition: CPOutlineView.j:58
id itemAtRow:(CPInteger aRow)
CPTableColumn outlineTableColumn()
unsigned rowHeight()
Definition: CPTableView.j:834
Definition: CPView.j:136
CPData archivedDataWithRootObject:(id anObject)
int numberOfColumns()
Definition: CPTableView.j:1601