API 0.9.5
AppKit/CPOutlineView.j
Go to the documentation of this file.
00001 /*
00002  * CPOutlineView.j
00003  * AppKit
00004  *
00005  * Created by Francisco Tolmasky.
00006  * Copyright 2009, 280 North, Inc.
00007  *
00008  * This library is free software; you can redistribute it and/or
00009  * modify it under the terms of the GNU Lesser General Public
00010  * License as published by the Free Software Foundation; either
00011  * version 2.1 of the License, or (at your option) any later version.
00012  *
00013  * This library is distributed in the hope that it will be useful,
00014  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
00016  * Lesser General Public License for more details.
00017  *
00018  * You should have received a copy of the GNU Lesser General Public
00019  * License along with this library; if not, write to the Free Software
00020  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
00021  */
00022 
00023 
00024 
00025 CPOutlineViewColumnDidMoveNotification          = @"CPOutlineViewColumnDidMoveNotification";
00026 CPOutlineViewColumnDidResizeNotification        = @"CPOutlineViewColumnDidResizeNotification";
00027 CPOutlineViewItemDidCollapseNotification        = @"CPOutlineViewItemDidCollapseNotification";
00028 CPOutlineViewItemDidExpandNotification          = @"CPOutlineViewItemDidExpandNotification";
00029 CPOutlineViewItemWillCollapseNotification       = @"CPOutlineViewItemWillCollapseNotification";
00030 CPOutlineViewItemWillExpandNotification         = @"CPOutlineViewItemWillExpandNotification";
00031 CPOutlineViewSelectionDidChangeNotification     = @"CPOutlineViewSelectionDidChangeNotification";
00032 CPOutlineViewSelectionIsChangingNotification    = @"CPOutlineViewSelectionIsChangingNotification";
00033 
00034 var CPOutlineViewDataSource_outlineView_setObjectValue_forTableColumn_byItem_                       = 1 << 1,
00035     CPOutlineViewDataSource_outlineView_shouldDeferDisplayingChildrenOfItem_                        = 1 << 2,
00036 
00037     CPOutlineViewDataSource_outlineView_acceptDrop_item_childIndex_                                 = 1 << 3,
00038     CPOutlineViewDataSource_outlineView_validateDrop_proposedItem_proposedChildIndex_               = 1 << 4,
00039     CPOutlineViewDataSource_outlineView_validateDrop_proposedRow_proposedDropOperation_             = 1 << 5,
00040     CPOutlineViewDataSource_outlineView_namesOfPromisedFilesDroppedAtDestination_forDraggedItems_   = 1 << 6,
00041 
00042     CPOutlineViewDataSource_outlineView_itemForPersistentObject_                                    = 1 << 7,
00043     CPOutlineViewDataSource_outlineView_persistentObjectForItem_                                    = 1 << 8,
00044 
00045     CPOutlineViewDataSource_outlineView_writeItems_toPasteboard_                                    = 1 << 9,
00046 
00047     CPOutlineViewDataSource_outlineView_sortDescriptorsDidChange_                                   = 1 << 10;
00048 
00049 var CPOutlineViewDelegate_outlineView_dataViewForTableColumn_item_                                  = 1 << 1,
00050     CPOutlineViewDelegate_outlineView_didClickTableColumn_                                          = 1 << 2,
00051     CPOutlineViewDelegate_outlineView_didDragTableColumn_                                           = 1 << 3,
00052     CPOutlineViewDelegate_outlineView_heightOfRowByItem_                                            = 1 << 4,
00053     CPOutlineViewDelegate_outlineView_isGroupItem_                                                  = 1 << 5,
00054     CPOutlineViewDelegate_outlineView_mouseDownInHeaderOfTableColumn_                               = 1 << 6,
00055     CPOutlineViewDelegate_outlineView_nextTypeSelectMatchFromItem_toItem_forString_                 = 1 << 7,
00056     CPOutlineViewDelegate_outlineView_selectionIndexesForProposedSelection_                         = 1 << 8,
00057     CPOutlineViewDelegate_outlineView_shouldCollapseItem_                                           = 1 << 9,
00058     CPOutlineViewDelegate_outlineView_shouldEditTableColumn_item_                                   = 1 << 10,
00059     CPOutlineViewDelegate_outlineView_shouldExpandItem_                                             = 1 << 11,
00060     CPOutlineViewDelegate_outlineView_shouldReorderColumn_toColumn_                                 = 1 << 12,
00061     CPOutlineViewDelegate_outlineView_shouldSelectItem_                                             = 1 << 13,
00062     CPOutlineViewDelegate_outlineView_shouldSelectTableColumn_                                      = 1 << 14,
00063     CPOutlineViewDelegate_outlineView_shouldShowOutlineViewForItem_                                 = 1 << 15,
00064     CPOutlineViewDelegate_outlineView_shouldShowViewExpansionForTableColumn_item_                   = 1 << 16,
00065     CPOutlineViewDelegate_outlineView_shouldTrackView_forTableColumn_item_                          = 1 << 17,
00066     CPOutlineViewDelegate_outlineView_shouldTypeSelectForEvent_withCurrentSearchString_             = 1 << 18,
00067     CPOutlineViewDelegate_outlineView_sizeToFitWidthOfColumn_                                       = 1 << 19,
00068     CPOutlineViewDelegate_outlineView_toolTipForView_rect_tableColumn_item_mouseLocation_           = 1 << 20,
00069     CPOutlineViewDelegate_outlineView_typeSelectStringForTableColumn_item_                          = 1 << 21,
00070     CPOutlineViewDelegate_outlineView_willDisplayOutlineView_forTableColumn_item_                   = 1 << 22,
00071     CPOutlineViewDelegate_outlineView_willDisplayView_forTableColumn_item_                          = 1 << 23,
00072     CPOutlineViewDelegate_selectionShouldChangeInOutlineView_                                       = 1 << 24,
00073     CPOutlineViewDelegate_outlineView_menuForTableColumn_item_                                      = 1 << 25;
00074 
00075 CPOutlineViewDropOnItemIndex = -1;
00076 
00077 var CPOutlineViewCoalesceSelectionNotificationStateOff  = 0,
00078     CPOutlineViewCoalesceSelectionNotificationStateOn   = 1,
00079     CPOutlineViewCoalesceSelectionNotificationStateDid  = 2;
00080 
00096 @implementation CPOutlineView : CPTableView
00097 {
00098     id              _outlineViewDataSource;
00099     id              _outlineViewDelegate;
00100     CPTableColumn   _outlineTableColumn;
00101 
00102     float           _indentationPerLevel;
00103     BOOL            _indentationMarkerFollowsDataView;
00104 
00105     CPInteger       _implementedOutlineViewDataSourceMethods;
00106     CPInteger       _implementedOutlineViewDelegateMethods;
00107 
00108     Object          _rootItemInfo;
00109     CPMutableArray  _itemsForRows;
00110     Object          _itemInfosForItems;
00111 
00112     CPControl       _disclosureControlPrototype;
00113     CPArray         _disclosureControlsForRows;
00114     CPData          _disclosureControlData;
00115     CPArray         _disclosureControlQueue;
00116 
00117     BOOL            _shouldRetargetItem;
00118     id              _retargetedItem;
00119 
00120     BOOL            _shouldRetargetChildIndex;
00121     CPInteger       _retargedChildIndex;
00122     CPTimer         _dragHoverTimer;
00123     id              _dropItem;
00124 
00125     BOOL            _coalesceSelectionNotificationState;
00126 }
00127 
00128 - (id)initWithFrame:(CGRect)aFrame
00129 {
00130     self = [super initWithFrame:aFrame];
00131 
00132     if (self)
00133     {
00134         _selectionHighlightStyle = CPTableViewSelectionHighlightStyleSourceList;
00135 
00136         // The root item has weight "0", thus represents the weight solely of its descendants.
00137         _rootItemInfo = { isExpanded:YES, isExpandable:NO, level:-1, row:-1, children:[], weight:0 };
00138 
00139         _itemsForRows = [];
00140         _itemInfosForItems = { };
00141         _disclosureControlsForRows = [];
00142 
00143         _retargetedItem = nil;
00144         _shouldRetargetItem = NO;
00145 
00146         _retargedChildIndex = nil;
00147         _shouldRetargetChildIndex = NO;
00148         _startHoverTime = nil;
00149 
00150         [self setIndentationPerLevel:16.0];
00151         [self setIndentationMarkerFollowsDataView:YES];
00152 
00153         [super setDataSource:[[_CPOutlineViewTableViewDataSource alloc] initWithOutlineView:self]];
00154         [super setDelegate:[[_CPOutlineViewTableViewDelegate alloc] initWithOutlineView:self]];
00155 
00156         [self setDisclosureControlPrototype:[[CPDisclosureButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 10.0)]];
00157     }
00158 
00159     return self;
00160 }
00212 - (void)setDataSource:(id)aDataSource
00213 {
00214     if (_outlineViewDataSource === aDataSource)
00215         return;
00216 
00217     if (![aDataSource respondsToSelector:@selector(outlineView:child:ofItem:)])
00218         [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:child:ofItem:'"];
00219 
00220     if (![aDataSource respondsToSelector:@selector(outlineView:isItemExpandable:)])
00221         [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:isItemExpandable:'"];
00222 
00223     if (![aDataSource respondsToSelector:@selector(outlineView:numberOfChildrenOfItem:)])
00224         [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:numberOfChildrenOfItem:'"];
00225 
00226     if (![aDataSource respondsToSelector:@selector(outlineView:objectValueForTableColumn:byItem:)])
00227         [CPException raise:CPInternalInconsistencyException reason:"Data source must implement 'outlineView:objectValueForTableColumn:byItem:'"];
00228 
00229     _outlineViewDataSource = aDataSource;
00230     _implementedOutlineViewDataSourceMethods = 0;
00231 
00232     if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:setObjectValue:forTableColumn:byItem:)])
00233         _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_setObjectValue_forTableColumn_byItem_;
00234 
00235     if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:shouldDeferDisplayingChildrenOfItem:)])
00236         _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_shouldDeferDisplayingChildrenOfItem_;
00237 
00238     if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:acceptDrop:item:childIndex:)])
00239         _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_acceptDrop_item_childIndex_;
00240 
00241     if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:validateDrop:proposedItem:proposedChildIndex:)])
00242         _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_validateDrop_proposedItem_proposedChildIndex_;
00243 
00244     if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:validateDrop:proposedRow:proposedDropOperation:)])
00245         _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_validateDrop_proposedRow_proposedDropOperation_;
00246 
00247     if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:namesOfPromisedFilesDroppedAtDestination:forDraggedItems:)])
00248         _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_namesOfPromisedFilesDroppedAtDestination_forDraggedItems_;
00249 
00250     if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:itemForPersistentObject:)])
00251         _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_itemForPersistentObject_;
00252 
00253     if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:persistentObjectForItem:)])
00254         _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_persistentObjectForItem_;
00255 
00256     if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:writeItems:toPasteboard:)])
00257         _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_writeItems_toPasteboard_;
00258 
00259     if ([_outlineViewDataSource respondsToSelector:@selector(outlineView:sortDescriptorsDidChange:)])
00260         _implementedOutlineViewDataSourceMethods |= CPOutlineViewDataSource_outlineView_sortDescriptorsDidChange_;
00261 
00262     [self reloadData];
00263 }
00264 
00270 - (id)dataSource
00271 {
00272     return _outlineViewDataSource;
00273 }
00274 
00282 - (BOOL)isExpandable:(id)anItem
00283 {
00284     if (!anItem)
00285         return YES;
00286 
00287     var itemInfo = _itemInfosForItems[[anItem UID]];
00288 
00289     if (!itemInfo)
00290         return NO;
00291 
00292     return itemInfo.isExpandable;
00293 }
00294 
00302 - (BOOL)isItemExpanded:(id)anItem
00303 {
00304     if (!anItem)
00305         return YES;
00306 
00307     var itemInfo = _itemInfosForItems[[anItem UID]];
00308 
00309     if (!itemInfo)
00310         return NO;
00311 
00312     return itemInfo.isExpanded;
00313 }
00314 
00320 - (void)expandItem:(id)anItem
00321 {
00322     [self expandItem:anItem expandChildren:NO];
00323 }
00324 
00331 - (void)expandItem:(id)anItem expandChildren:(BOOL)shouldExpandChildren
00332 {
00333     var itemInfo = null;
00334 
00335     if (!anItem)
00336         itemInfo = _rootItemInfo;
00337     else
00338         itemInfo = _itemInfosForItems[[anItem UID]];
00339 
00340     if (!itemInfo)
00341         return;
00342 
00343     // When shouldExpandChildren is YES, we need to make sure we're collecting
00344     // selection notifications so that exactly one IsChanging and one
00345     // DidChange is sent as needed, for the totality of the operation.
00346     var isTopLevel = NO;
00347     if (!_coalesceSelectionNotificationState)
00348     {
00349         isTopLevel = YES;
00350         _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOn;
00351     }
00352 
00353     // To prevent items which are already expanded from firing notifications.
00354     if (!itemInfo.isExpanded)
00355     {
00356         [self _noteItemWillExpand:anItem];
00357 
00358         var previousRowCount = [self numberOfRows];
00359 
00360         itemInfo.isExpanded = YES;
00361         // XXX Shouldn't the items reload before the notification is sent?
00362         [self _noteItemDidExpand:anItem];
00363         [self reloadItem:anItem reloadChildren:YES];
00364 
00365         // Shift selection indexes below so that the same items remain selected.
00366         var rowCountDelta = [self numberOfRows] - previousRowCount;
00367         if (rowCountDelta)
00368         {
00369             var selection = [self selectedRowIndexes],
00370                 expandIndex = [self rowForItem:anItem] + 1;
00371 
00372             if ([selection intersectsIndexesInRange:CPMakeRange(expandIndex, _itemsForRows.length)])
00373             {
00374                 [self _noteSelectionIsChanging];
00375                 [selection shiftIndexesStartingAtIndex:expandIndex by:rowCountDelta];
00376                 [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
00377             }
00378         }
00379     }
00380 
00381     if (shouldExpandChildren)
00382     {
00383         var children = itemInfo.children,
00384             childIndex = children.length;
00385 
00386         while (childIndex--)
00387             [self expandItem:children[childIndex] expandChildren:YES];
00388     }
00389 
00390     if (isTopLevel)
00391     {
00392         var r = _coalesceSelectionNotificationState;
00393         _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOff;
00394         if (r === CPOutlineViewCoalesceSelectionNotificationStateDid)
00395             [self _noteSelectionDidChange];
00396     }
00397 }
00398 
00404 - (void)collapseItem:(id)anItem
00405 {
00406     if (!anItem)
00407         return;
00408 
00409     var itemInfo = _itemInfosForItems[[anItem UID]];
00410 
00411     if (!itemInfo)
00412         return;
00413 
00414     if (!itemInfo.isExpanded)
00415         return;
00416 
00417     // Don't spam notifications.
00418     _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOn;
00419 
00420     [self _noteItemWillCollapse:anItem];
00421     // Update selections:
00422     // * Deselect items inside the collapsed item.
00423     // * Shift row selections below the collapsed item so that the same logical items remain selected.
00424     var collapseTopIndex = [self rowForItem:anItem],
00425         topLevel = [self levelForRow:collapseTopIndex],
00426         collapseEndIndex = collapseTopIndex;
00427 
00428     while (collapseEndIndex + 1 < _itemsForRows.length && [self levelForRow:collapseEndIndex + 1] > topLevel)
00429         collapseEndIndex++;
00430 
00431     var collapseRange = CPMakeRange(collapseTopIndex + 1, collapseEndIndex - collapseTopIndex);
00432 
00433     if (collapseRange.length)
00434     {
00435         var selection = [self selectedRowIndexes];
00436 
00437         if ([selection intersectsIndexesInRange:collapseRange])
00438         {
00439             [self _noteSelectionIsChanging];
00440             [selection removeIndexesInRange:collapseRange];
00441             [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
00442         }
00443 
00444         // Shift any selected rows below upwards.
00445         if ([selection intersectsIndexesInRange:CPMakeRange(collapseEndIndex + 1, _itemsForRows.length)])
00446         {
00447             [self _noteSelectionIsChanging];
00448             [selection shiftIndexesStartingAtIndex:collapseEndIndex + 1 by:-collapseRange.length];
00449             [self _setSelectedRowIndexes:selection]; // _noteSelectionDidChange will be suppressed.
00450         }
00451     }
00452     itemInfo.isExpanded = NO;
00453 
00454     // XXX Shouldn't the items reload before the notification is sent?
00455     [self _noteItemDidCollapse:anItem];
00456     [self reloadItem:anItem reloadChildren:YES];
00457 
00458     // Send selection notifications only after the items have loaded so that
00459     // the new selection is consistent with the actual rows for any observers.
00460     var r = _coalesceSelectionNotificationState;
00461     _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateOff;
00462     if (r === CPOutlineViewCoalesceSelectionNotificationStateDid)
00463         [self _noteSelectionDidChange];
00464 }
00465 
00471 - (void)reloadItem:(id)anItem
00472 {
00473     [self reloadItem:anItem reloadChildren:NO];
00474 }
00475 
00482 - (void)reloadItem:(id)anItem reloadChildren:(BOOL)shouldReloadChildren
00483 {
00484     if (!!shouldReloadChildren || !anItem)
00485         _loadItemInfoForItem(self, anItem);
00486     else
00487         _reloadItem(self, anItem);
00488 
00489     [super reloadData];
00490 }
00491 
00498 - (id)itemAtRow:(CPInteger)aRow
00499 {
00500     return _itemsForRows[aRow] || nil;
00501 }
00502 
00509 - (CPInteger)rowForItem:(id)anItem
00510 {
00511     if (!anItem)
00512         return _rootItemInfo.row;
00513 
00514     var itemInfo = _itemInfosForItems[[anItem UID]];
00515 
00516     if (!itemInfo)
00517         return CPNotFound;
00518 
00519     return itemInfo.row;
00520 }
00521 
00528 - (void)setOutlineTableColumn:(CPTableColumn)aTableColumn
00529 {
00530     if (_outlineTableColumn === aTableColumn)
00531         return;
00532 
00533     _outlineTableColumn = aTableColumn;
00534 
00535     // FIXME: efficiency.
00536     [self reloadData];
00537 }
00538 
00544 - (CPTableColumn)outlineTableColumn
00545 {
00546     return _outlineTableColumn;
00547 }
00548 
00557 - (CPInteger)levelForItem:(id)anItem
00558 {
00559     if (!anItem)
00560         return _rootItemInfo.level;
00561 
00562     var itemInfo = _itemInfosForItems[[anItem UID]];
00563 
00564     if (!itemInfo)
00565         return CPNotFound;
00566 
00567     return itemInfo.level;
00568 }
00569 
00577 - (CPInteger)levelForRow:(CPInteger)aRow
00578 {
00579     return [self levelForItem:[self itemAtRow:aRow]];
00580 }
00581 
00587 - (void)setIndentationPerLevel:(float)anIndentationWidth
00588 {
00589     if (_indentationPerLevel === anIndentationWidth)
00590         return;
00591 
00592     _indentationPerLevel = anIndentationWidth;
00593 
00594     // FIXME: efficiency!!!!
00595     [self reloadData];
00596 }
00597 
00603 - (float)indentationPerLevel
00604 {
00605     return _indentationPerLevel;
00606 }
00607 
00615 - (void)setIndentationMarkerFollowsDataView:(BOOL)indentationMarkerShouldFollowDataView
00616 {
00617     if (_indentationMarkerFollowsDataView === indentationMarkerShouldFollowDataView)
00618         return;
00619 
00620     _indentationMarkerFollowsDataView = indentationMarkerShouldFollowDataView;
00621 
00622     // !!!!
00623     [self reloadData];
00624 }
00625 
00633 - (BOOL)indentationMarkerFollowsDataView
00634 {
00635     return _indentationMarkerFollowsDataView;
00636 }
00637 
00645 - (id)parentForItem:(id)anItem
00646 {
00647     if (!anItem)
00648         return nil;
00649 
00650     var itemInfo = _itemInfosForItems[[anItem UID]];
00651 
00652     if (!itemInfo)
00653         return nil;
00654 
00655     var parent = itemInfo.parent;
00656 
00657     // Check if the parent is the root item because we never return the actual root item
00658     if (itemInfo[[parent UID]] === _rootItemInfo)
00659         parent = nil;
00660 
00661     return parent;
00662 }
00663 
00669 - (CGRect)_frameOfOutlineDataViewAtRow:(CPInteger)aRow
00670 {
00671     var columnIndex = [[self tableColumns] indexOfObject:_outlineTableColumn],
00672         frame = [super frameOfDataViewAtColumn:columnIndex row:aRow],
00673         indentationWidth = ([self levelForRow:aRow] + 1) * [self indentationPerLevel];
00674 
00675     frame.origin.x += indentationWidth;
00676     frame.size.width -= indentationWidth;
00677 
00678     return frame;
00679 }
00680 
00689 - (CGRect)frameOfOutlineDisclosureControlAtRow:(CPInteger)aRow
00690 {
00691     if (![self isExpandable:[self itemAtRow:aRow]])
00692         return _CGRectMakeZero();
00693 
00694     var dataViewFrame = [self _frameOfOutlineDataViewAtRow:aRow],
00695         frame = _CGRectMake(_CGRectGetMinX(dataViewFrame) - 10, _CGRectGetMinY(dataViewFrame), 10, _CGRectGetHeight(dataViewFrame));
00696 
00697     return frame;
00698 }
00699 
00704 - (void)_performSelection:(BOOL)select forRow:(CPInteger)rowIndex context:(id)context
00705 {
00706     [super _performSelection:select forRow:rowIndex context:context];
00707 
00708     var control = _disclosureControlsForRows[rowIndex],
00709         selector = select ? @"setThemeState:" : @"unsetThemeState:";
00710 
00711     [control performSelector:CPSelectorFromString(selector) withObject:CPThemeStateSelected];
00712 }
00713 
00790 - (void)setDelegate:(id)aDelegate
00791 {
00792     if (_outlineViewDelegate === aDelegate)
00793         return;
00794 
00795     var defaultCenter = [CPNotificationCenter defaultCenter];
00796 
00797     if (_outlineViewDelegate)
00798     {
00799         if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidMove:)])
00800             [defaultCenter
00801                 removeObserver:_outlineViewDelegate
00802                           name:CPOutlineViewColumnDidMoveNotification
00803                         object:self];
00804 
00805         if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidResize:)])
00806             [defaultCenter
00807                 removeObserver:_outlineViewDelegate
00808                           name:CPOutlineViewColumnDidResizeNotification
00809                         object:self];
00810 
00811         if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionDidChange:)])
00812             [defaultCenter
00813                 removeObserver:_outlineViewDelegate
00814                           name:CPOutlineViewSelectionDidChangeNotification
00815                         object:self];
00816 
00817         if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionIsChanging:)])
00818             [defaultCenter
00819                 removeObserver:_outlineViewDelegate
00820                           name:CPOutlineViewSelectionIsChangingNotification
00821                         object:self];
00822 
00823 
00824 
00825         if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillExpand:)])
00826             [defaultCenter
00827                 removeObserver:_outlineViewDelegate
00828                           name:CPOutlineViewItemWillExpandNotification
00829                         object:self];
00830 
00831 
00832         if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidExpand:)])
00833             [defaultCenter
00834                 removeObserver:_outlineViewDelegate
00835                           name:CPOutlineViewItemDidExpandNotification
00836                         object:self];
00837 
00838 
00839         if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillCollapse:)])
00840             [defaultCenter
00841                 removeObserver:_outlineViewDelegate
00842                           name:CPOutlineViewItemWillCollapseNotification
00843                         object:self];
00844 
00845 
00846         if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidCollapse:)])
00847             [defaultCenter
00848                 removeObserver:_outlineViewDelegate
00849                           name:CPOutlineViewItemDidCollapseNotification
00850                         object:self];
00851     }
00852 
00853     _outlineViewDelegate = aDelegate;
00854     _implementedOutlineViewDelegateMethods = 0;
00855 
00856     var delegateMethods = [
00857             CPOutlineViewDelegate_outlineView_dataViewForTableColumn_item_                       , @selector(outlineView:dataViewForTableColumn:item:),
00858             CPOutlineViewDelegate_outlineView_didClickTableColumn_                               , @selector(outlineView:didClickTableColumn:),
00859             CPOutlineViewDelegate_outlineView_didDragTableColumn_                                , @selector(outlineView:didDragTableColumn:),
00860             CPOutlineViewDelegate_outlineView_heightOfRowByItem_                                 , @selector(outlineView:heightOfRowByItem:),
00861             CPOutlineViewDelegate_outlineView_isGroupItem_                                       , @selector(outlineView:isGroupItem:),
00862             CPOutlineViewDelegate_outlineView_mouseDownInHeaderOfTableColumn_                    , @selector(outlineView:mouseDownInHeaderOfTableColumn:),
00863             CPOutlineViewDelegate_outlineView_nextTypeSelectMatchFromItem_toItem_forString_      , @selector(outlineView:nextTypeSelectMatchFromItem:toItem:forString:),
00864             CPOutlineViewDelegate_outlineView_selectionIndexesForProposedSelection_              , @selector(outlineView:selectionIndexesForProposedSelection:),
00865             CPOutlineViewDelegate_outlineView_shouldCollapseItem_                                , @selector(outlineView:shouldCollapseItem:),
00866             CPOutlineViewDelegate_outlineView_shouldEditTableColumn_item_                        , @selector(outlineView:shouldEditTableColumn:item:),
00867             CPOutlineViewDelegate_outlineView_shouldExpandItem_                                  , @selector(outlineView:shouldExpandItem:),
00868             CPOutlineViewDelegate_outlineView_shouldReorderColumn_toColumn_                      , @selector(outlineView:shouldReorderColumn:toColumn:),
00869             CPOutlineViewDelegate_outlineView_shouldSelectItem_                                  , @selector(outlineView:shouldSelectItem:),
00870             CPOutlineViewDelegate_outlineView_shouldSelectTableColumn_                           , @selector(outlineView:shouldSelectTableColumn:),
00871             CPOutlineViewDelegate_outlineView_shouldShowOutlineViewForItem_                      , @selector(outlineView:shouldShowOutlineViewForItem:),
00872             CPOutlineViewDelegate_outlineView_shouldShowViewExpansionForTableColumn_item_        , @selector(outlineView:shouldShowViewExpansionForTableColumn:item:),
00873             CPOutlineViewDelegate_outlineView_shouldTrackView_forTableColumn_item_               , @selector(outlineView:shouldTrackView:forTableColumn:item:),
00874             CPOutlineViewDelegate_outlineView_shouldTypeSelectForEvent_withCurrentSearchString_  , @selector(outlineView:shouldTypeSelectForEvent:withCurrentSearchString:),
00875             CPOutlineViewDelegate_outlineView_sizeToFitWidthOfColumn_                            , @selector(outlineView:sizeToFitWidthOfColumn:),
00876             CPOutlineViewDelegate_outlineView_toolTipForView_rect_tableColumn_item_mouseLocation_, @selector(outlineView:toolTipForView:rect:tableColumn:item:mouseLocation:),
00877             CPOutlineViewDelegate_outlineView_typeSelectStringForTableColumn_item_               , @selector(outlineView:typeSelectStringForTableColumn:item:),
00878             CPOutlineViewDelegate_outlineView_willDisplayOutlineView_forTableColumn_item_        , @selector(outlineView:willDisplayOutlineView:forTableColumn:item:),
00879             CPOutlineViewDelegate_outlineView_willDisplayView_forTableColumn_item_               , @selector(outlineView:willDisplayView:forTableColumn:item:),
00880             CPOutlineViewDelegate_selectionShouldChangeInOutlineView_                            , @selector(selectionShouldChangeInOutlineView:),
00881             CPOutlineViewDelegate_outlineView_menuForTableColumn_item_                           , @selector(outlineView:menuForTableColumn:item:)
00882         ],
00883         delegateCount = [delegateMethods count];
00884 
00885     for (var i = 0; i < delegateCount; i += 2)
00886     {
00887         var bitMask = delegateMethods[i],
00888             selector = delegateMethods[i + 1];
00889 
00890         if ([_outlineViewDelegate respondsToSelector:selector])
00891             _implementedOutlineViewDelegateMethods |= bitMask;
00892     }
00893 
00894     if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidMove:)])
00895         [defaultCenter
00896             addObserver:_outlineViewDelegate
00897             selector:@selector(outlineViewColumnDidMove:)
00898             name:CPOutlineViewColumnDidMoveNotification
00899             object:self];
00900 
00901     if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewColumnDidResize:)])
00902         [defaultCenter
00903             addObserver:_outlineViewDelegate
00904             selector:@selector(outlineViewColumnDidMove:)
00905             name:CPOutlineViewColumnDidResizeNotification
00906             object:self];
00907 
00908     if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionDidChange:)])
00909         [defaultCenter
00910             addObserver:_outlineViewDelegate
00911             selector:@selector(outlineViewSelectionDidChange:)
00912             name:CPOutlineViewSelectionDidChangeNotification
00913             object:self];
00914 
00915     if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewSelectionIsChanging:)])
00916         [defaultCenter
00917             addObserver:_outlineViewDelegate
00918             selector:@selector(outlineViewSelectionIsChanging:)
00919             name:CPOutlineViewSelectionIsChangingNotification
00920             object:self];
00921 
00922 
00923     if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillExpand:)])
00924         [defaultCenter
00925             addObserver:_outlineViewDelegate
00926             selector:@selector(outlineViewItemWillExpand:)
00927             name:CPOutlineViewItemWillExpandNotification
00928             object:self];
00929 
00930     if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidExpand:)])
00931         [defaultCenter
00932             addObserver:_outlineViewDelegate
00933             selector:@selector(outlineViewItemDidExpand:)
00934             name:CPOutlineViewItemDidExpandNotification
00935             object:self];
00936 
00937     if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemWillCollapse:)])
00938         [defaultCenter
00939             addObserver:_outlineViewDelegate
00940             selector:@selector(outlineViewItemWillCollapse:)
00941             name:CPOutlineViewItemWillCollapseNotification
00942             object:self];
00943 
00944     if ([_outlineViewDelegate respondsToSelector:@selector(outlineViewItemDidCollapse:)])
00945         [defaultCenter
00946             addObserver:_outlineViewDelegate
00947             selector:@selector(outlineViewItemDidCollapse:)
00948             name:CPOutlineViewItemDidCollapseNotification
00949             object:self];
00950 
00951 }
00952 
00953 - (BOOL)_sendDelegateDeleteKeyPressed
00954 {
00955     if ([[self delegate] respondsToSelector: @selector(outlineViewDeleteKeyPressed:)])
00956     {
00957         [[self delegate] outlineViewDeleteKeyPressed:self];
00958         return YES;
00959     }
00960 
00961     return NO;
00962 }
00963 
00967 - (id)delegate
00968 {
00969     return _outlineViewDelegate;
00970 }
00971 
00979 - (void)setDisclosureControlPrototype:(CPControl)aControl
00980 {
00981     _disclosureControlPrototype = aControl;
00982     _disclosureControlData = nil;
00983     _disclosureControlQueue = [];
00984 
00985     // fIXME: really?
00986     [self reloadData];
00987 }
00988 
00992 - (void)reloadData
00993 {
00994     [self reloadItem:nil reloadChildren:YES];
00995 }
00996 
01007 - (void)addTableColumn:(CPTableColumn)aTableColumn
01008 {
01009     [super addTableColumn:aTableColumn];
01010 
01011     if ([self numberOfColumns] === 1)
01012         _outlineTableColumn = aTableColumn;
01013 }
01017 - (void)removeTableColumn:(CPTableColumn)aTableColumn
01018 {
01019     if (aTableColumn === [self outlineTableColumn])
01020         CPLog("CPOutlineView cannot remove outlineTableColumn with removeTableColumn:. User setOutlineTableColumn: instead.");
01021     else
01022         [super removeTableColumn:aTableColumn];
01023 }
01029 - (CGRect)frameOfDataViewAtColumn:(CPInteger)aColumn row:(CPInteger)aRow
01030 {
01031     var tableColumn = [self tableColumns][aColumn];
01032 
01033     if (tableColumn === _outlineTableColumn)
01034         return [self _frameOfOutlineDataViewAtRow:aRow];
01035 
01036     return [super frameOfDataViewAtColumn:aColumn row:aRow];
01037 }
01038 
01043 - (CPView)_dragViewForColumn:(int)theColumnIndex event:(CPEvent)theDragEvent offset:(CPPointPointer)theDragViewOffset
01044 {
01045     var dragView = [[_CPColumnDragView alloc] initWithLineColor:[self gridColor]],
01046         tableColumn = [[self tableColumns] objectAtIndex:theColumnIndex],
01047         bounds = _CGRectMake(0.0, 0.0, [tableColumn width], _CGRectGetHeight([self exposedRect]) + 23.0),
01048         columnRect = [self rectOfColumn:theColumnIndex],
01049         headerView = [tableColumn headerView],
01050         row = [_exposedRows firstIndex];
01051 
01052     while (row !== CPNotFound)
01053     {
01054         var dataView = [self _newDataViewForRow:row tableColumn:tableColumn],
01055             dataViewFrame = [self frameOfDataViewAtColumn:theColumnIndex row:row];
01056 
01057         // Only one column is ever dragged so we just place the view at
01058         dataViewFrame.origin.x = 0.0;
01059 
01060         // Offset by table header height - scroll position
01061         dataViewFrame.origin.y = ( _CGRectGetMinY(dataViewFrame) - _CGRectGetMinY([self exposedRect]) ) + 23.0;
01062         [dataView setFrame:dataViewFrame];
01063 
01064         [dataView setObjectValue:[self _objectValueForTableColumn:tableColumn row:row]];
01065 
01066 
01067         if (tableColumn === _outlineTableColumn)
01068         {
01069             // first inset the dragview
01070             var indentationWidth = ([self levelForRow:row] + 1) * [self indentationPerLevel];
01071 
01072             dataViewFrame.origin.x += indentationWidth;
01073             dataViewFrame.size.width -= indentationWidth;
01074 
01075             [dataView setFrame:dataViewFrame];
01076         }
01077 
01078         [dragView addSubview:dataView];
01079 
01080         row = [_exposedRows indexGreaterThanIndex:row];
01081     }
01082 
01083     // Add the column header view
01084     var headerFrame = [headerView frame];
01085     headerFrame.origin = _CGPointMakeZero();
01086 
01087     var columnHeaderView = [[_CPTableColumnHeaderView alloc] initWithFrame:headerFrame];
01088     [columnHeaderView setStringValue:[headerView stringValue]];
01089     [columnHeaderView setThemeState:[headerView themeState]];
01090     [dragView addSubview:columnHeaderView];
01091 
01092     [dragView setBackgroundColor:[CPColor whiteColor]];
01093     [dragView setAlphaValue:0.7];
01094     [dragView setFrame:bounds];
01095 
01096     return dragView;
01097 }
01098 
01114 - (void)setDropItem:(id)theItem dropChildIndex:(int)theIndex
01115 {
01116     if (_dropItem !== theItem && theIndex < 0 && [self isExpandable:theItem] && ![self isItemExpanded:theItem])
01117     {
01118         if (_dragHoverTimer)
01119             [_dragHoverTimer invalidate];
01120 
01121         var autoExpandCallBack = function(){
01122             if (_dropItem)
01123             {
01124                 [_dropOperationFeedbackView blink];
01125                 [CPTimer scheduledTimerWithTimeInterval:.3 callback:objj_msgSend(self, "expandItem:", _dropItem) repeats:NO];
01126             }
01127         }
01128 
01129         _dragHoverTimer = [CPTimer scheduledTimerWithTimeInterval:.8 callback:autoExpandCallBack repeats:NO];
01130     }
01131 
01132     if (theIndex >= 0)
01133     {
01134         [_dragHoverTimer invalidate];
01135         _dragHoverTimer = nil;
01136     }
01137 
01138     _dropItem = theItem;
01139     _retargetedItem = theItem;
01140     _shouldRetargetItem = YES;
01141 
01142     _retargedChildIndex = theIndex;
01143     _shouldRetargetChildIndex = YES;
01144 
01145     // set CPTableView's _retargetedDropRow based on retargetedItem and retargetedChildIndex
01146     var retargetedItemInfo = (_retargetedItem !== nil) ? _itemInfosForItems[[_retargetedItem UID]] : _rootItemInfo;
01147 
01148     if (_retargedChildIndex === [retargetedItemInfo.children count])
01149     {
01150         var retargetedChildItem = [retargetedItemInfo.children lastObject];
01151         _retargetedDropRow = [self rowForItem:retargetedChildItem] + 1;
01152     }
01153     else
01154     {
01155         var retargetedChildItem = (_retargedChildIndex !== CPOutlineViewDropOnItemIndex) ? retargetedItemInfo.children[_retargedChildIndex] : _retargetedItem;
01156         _retargetedDropRow = [self rowForItem:retargetedChildItem];
01157     }
01158 }
01159 
01163 - (void)_draggingEnded
01164 {
01165     [super _draggingEnded];
01166     _dropItem = nil;
01167     [_dragHoverTimer invalidate];
01168     _dragHoverTimer = nil;
01169 }
01170 
01174 - (id)_parentItemForUpperRow:(int)theUpperRowIndex andLowerRow:(int)theLowerRowIndex atMouseOffset:(CPPoint)theOffset
01175 {
01176     if (_shouldRetargetItem)
01177         return _retargetedItem;
01178 
01179     var lowerLevel = [self levelForRow:theLowerRowIndex],
01180         upperItem = [self itemAtRow:theUpperRowIndex],
01181         upperLevel = [self levelForItem:upperItem];
01182 
01183     // If the row above us has a higher level the item can be added to multiple parent items
01184     // Determine which one by looping through all possible parents and return the first
01185     // of which the indentation level is larger than the current x offset
01186     while (upperLevel > lowerLevel)
01187     {
01188         upperLevel = [self levelForItem:upperItem];
01189 
01190         // See if this item's indentation level matches the mouse offset
01191         if (theOffset.x > (upperLevel + 1) * [self indentationPerLevel])
01192             return [self parentForItem:upperItem];
01193 
01194         // Check the next parent
01195         upperItem = [self parentForItem:upperItem];
01196     }
01197 
01198     return [self parentForItem:[self itemAtRow:theLowerRowIndex]];
01199 }
01200 
01204 - (CPRect)_rectForDropHighlightViewBetweenUpperRow:(int)theUpperRowIndex andLowerRow:(int)theLowerRowIndex offset:(CPPoint)theOffset
01205 {
01206     // Call super and the update x to reflect the current indentation level
01207     var rect = [super _rectForDropHighlightViewBetweenUpperRow:theUpperRowIndex andLowerRow:theLowerRowIndex offset:theOffset],
01208         parentItem = [self _parentItemForUpperRow:theUpperRowIndex andLowerRow:theLowerRowIndex atMouseOffset:theOffset],
01209         level = [self levelForItem:parentItem];
01210 
01211     rect.origin.x = (level + 1) * [self indentationPerLevel];
01212     rect.size.width -= rect.origin.x; // This assumes that the x returned by super is zero
01213 
01214     return rect;
01215 }
01216 
01221 - (void)_layoutDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
01222 {
01223     var rowArray = [],
01224         columnArray = [];
01225 
01226     [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
01227     [columns getIndexes:columnArray maxCount:-1 inIndexRange:nil];
01228 
01229     var columnIndex = 0,
01230         columnsCount = columnArray.length;
01231 
01232     for (; columnIndex < columnsCount; ++columnIndex)
01233     {
01234         var column = columnArray[columnIndex],
01235             tableColumn = _tableColumns[column],
01236             tableColumnUID = [tableColumn UID],
01237             dataViewsForTableColumn = _dataViewsForTableColumns[tableColumnUID],
01238             rowIndex = 0,
01239             rowsCount = rowArray.length;
01240 
01241         for (; rowIndex < rowsCount; ++rowIndex)
01242         {
01243             var row = rowArray[rowIndex],
01244                 dataView = dataViewsForTableColumn[row],
01245                 dataViewFrame = [self frameOfDataViewAtColumn:column row:row];
01246 
01247             [dataView setFrame:dataViewFrame];
01248 
01249             if (tableColumn === _outlineTableColumn)
01250             {
01251                 var control = _disclosureControlsForRows[row],
01252                     frame = [self frameOfOutlineDisclosureControlAtRow:row];
01253 
01254                 [control setFrame:frame];
01255             }
01256         }
01257     }
01258 }
01259 
01263 - (void)_loadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
01264 {
01265     [super _loadDataViewsInRows:rows columns:columns];
01266 
01267     var outlineColumn = [[self tableColumns] indexOfObjectIdenticalTo:[self outlineTableColumn]];
01268 
01269     if (![columns containsIndex:outlineColumn] ||  [self outlineTableColumn] === _draggedColumn)
01270         return;
01271 
01272     var rowArray = [];
01273 
01274     [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
01275 
01276     var rowIndex = 0,
01277         rowsCount = rowArray.length;
01278 
01279     for (; rowIndex < rowsCount; ++rowIndex)
01280     {
01281         var row = rowArray[rowIndex],
01282             item = _itemsForRows[row],
01283             isExpandable = [self isExpandable:item];
01284 
01285        if (!isExpandable)
01286             continue;
01287 
01288         var control = [self _dequeueDisclosureControl];
01289 
01290         _disclosureControlsForRows[row] = control;
01291 
01292         [control setState:[self isItemExpanded:item] ? CPOnState : CPOffState];
01293         var selector = [self isRowSelected:row] ? @"setThemeState:" : @"unsetThemeState:";
01294         [control performSelector:CPSelectorFromString(selector) withObject:CPThemeStateSelected];
01295         [control setFrame:[self frameOfOutlineDisclosureControlAtRow:row]];
01296 
01297         [self addSubview:control];
01298     }
01299 }
01300 
01304 - (void)_unloadDataViewsInRows:(CPIndexSet)rows columns:(CPIndexSet)columns
01305 {
01306     [super _unloadDataViewsInRows:rows columns:columns];
01307 
01308     var outlineColumn = [[self tableColumns] indexOfObjectIdenticalTo:[self outlineTableColumn]];
01309 
01310     if (![columns containsIndex:outlineColumn])
01311         return;
01312 
01313     var rowArray = [];
01314 
01315     [rows getIndexes:rowArray maxCount:-1 inIndexRange:nil];
01316 
01317     var rowIndex = 0,
01318         rowsCount = rowArray.length;
01319 
01320     for (; rowIndex < rowsCount; ++rowIndex)
01321     {
01322         var row = rowArray[rowIndex],
01323             control = _disclosureControlsForRows[row];
01324 
01325         if (!control)
01326             continue;
01327 
01328         [control removeFromSuperview];
01329 
01330         [self _enqueueDisclosureControl:control];
01331 
01332         _disclosureControlsForRows[row] = nil;
01333     }
01334 }
01335 
01339 - (void)_toggleFromDisclosureControl:(CPControl)aControl
01340 {
01341     var controlFrame = [aControl frame],
01342         item = [self itemAtRow:[self rowAtPoint:_CGPointMake(_CGRectGetMinX(controlFrame), _CGRectGetMidY(controlFrame))]];
01343 
01344     if ([self isItemExpanded:item])
01345         [self collapseItem:item];
01346 
01347     else
01348         [self expandItem:item];
01349 }
01350 
01354 - (void)_enqueueDisclosureControl:(CPControl)aControl
01355 {
01356     _disclosureControlQueue.push(aControl);
01357 }
01358 
01362 - (CPControl)_dequeueDisclosureControl
01363 {
01364     if (_disclosureControlQueue.length)
01365         return _disclosureControlQueue.pop();
01366 
01367     if (!_disclosureControlData)
01368         if (!_disclosureControlPrototype)
01369             return nil;
01370         else
01371             _disclosureControlData = [CPKeyedArchiver archivedDataWithRootObject:_disclosureControlPrototype];
01372 
01373     var disclosureControl = [CPKeyedUnarchiver unarchiveObjectWithData:_disclosureControlData];
01374 
01375     [disclosureControl setTarget:self];
01376     [disclosureControl setAction:@selector(_toggleFromDisclosureControl:)];
01377 
01378     return disclosureControl;
01379 }
01380 
01384 - (void)_noteSelectionIsChanging
01385 {
01386     if (!_coalesceSelectionNotificationState || _coalesceSelectionNotificationState === CPOutlineViewCoalesceSelectionNotificationStateOn)
01387     {
01388         [[CPNotificationCenter defaultCenter]
01389             postNotificationName:CPOutlineViewSelectionIsChangingNotification
01390                           object:self
01391                         userInfo:nil];
01392     }
01393 
01394     if (_coalesceSelectionNotificationState === CPOutlineViewCoalesceSelectionNotificationStateOn)
01395         _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateDid;
01396 }
01397 
01401 - (void)_noteSelectionDidChange
01402 {
01403     if (!_coalesceSelectionNotificationState)
01404     {
01405         [[CPNotificationCenter defaultCenter]
01406             postNotificationName:CPOutlineViewSelectionDidChangeNotification
01407                           object:self
01408                         userInfo:nil];
01409     }
01410 
01411     if (_coalesceSelectionNotificationState === CPOutlineViewCoalesceSelectionNotificationStateOn)
01412         _coalesceSelectionNotificationState = CPOutlineViewCoalesceSelectionNotificationStateDid;
01413 }
01414 
01418 - (void)_noteItemWillExpand:(id)item
01419 {
01420     [[CPNotificationCenter defaultCenter]
01421         postNotificationName:CPOutlineViewItemWillExpandNotification
01422                       object:self
01423                     userInfo:[CPDictionary dictionaryWithObject:item forKey:"CPObject"]];
01424 }
01425 
01429 - (void)_noteItemDidExpand:(id)item
01430 {
01431     [[CPNotificationCenter defaultCenter]
01432         postNotificationName:CPOutlineViewItemDidExpandNotification
01433                       object:self
01434                     userInfo:[CPDictionary dictionaryWithObject:item forKey:"CPObject"]];
01435 }
01436 
01440 - (void)_noteItemWillCollapse:(id)item
01441 {
01442     [[CPNotificationCenter defaultCenter]
01443         postNotificationName:CPOutlineViewItemWillCollapseNotification
01444                       object:self
01445                     userInfo:[CPDictionary dictionaryWithObject:item forKey:"CPObject"]];
01446 }
01447 
01451 - (void)_noteItemDidCollapse:(id)item
01452 {
01453     [[CPNotificationCenter defaultCenter]
01454         postNotificationName:CPOutlineViewItemDidCollapseNotification
01455                       object:self
01456                     userInfo:[CPDictionary dictionaryWithObject:item forKey:"CPObject"]];
01457 }
01458 
01459 
01460 - (void)keyDown:(CPEvent)anEvent
01461 {
01462     var character = [anEvent charactersIgnoringModifiers],
01463         modifierFlags = [anEvent modifierFlags];
01464 
01465     // Check for the key events manually, as opposed to waiting for CPWindow to sent the actual action message
01466     // in _processKeyboardUIKey:, because we might not want to handle the arrow events.
01467 
01468     if (character !== CPRightArrowFunctionKey && character !== CPLeftArrowFunctionKey) 
01469         return [super keyDown:anEvent];
01470 
01471     var rows = [self selectedRowIndexes],
01472         indexes = [],
01473         items = [];
01474 
01475     [rows getIndexes:indexes maxCount:-1 inIndexRange:nil];
01476 
01477     var i = 0,
01478         c = [indexes count];
01479 
01480     for (; i < c; i++)
01481         items.push([self itemAtRow:indexes[i]]);
01482    
01483 
01484     if (character === CPRightArrowFunctionKey)
01485     {
01486         for (var i = 0; i < c; i++)
01487             [self expandItem:items[i]];
01488     }
01489     else if (character === CPLeftArrowFunctionKey)
01490     {
01491         for (var i = 0; i < c; i++)
01492             [self collapseItem:items[i]]; 
01493     }
01494 
01495     [super keyDown:anEvent];
01496 }
01497 @end
01498 
01499 // FIX ME: We're using with() here because Safari fails if we use anOutlineView._itemInfosForItems or whatever...
01500 var _reloadItem = function(/*CPOutlineView*/ anOutlineView, /*id*/ anItem)
01501 {
01502     if (!anItem)
01503         return;
01504 
01505     with (anOutlineView)
01506     {
01507         // Get the existing info if it exists.
01508         var itemInfosForItems = _itemInfosForItems,
01509             dataSource = _outlineViewDataSource,
01510             itemUID = [anItem UID],
01511             itemInfo = itemInfosForItems[itemUID];
01512 
01513         // If we're not in the tree, then just bail.
01514         if (!itemInfo)
01515             return [];
01516 
01517         // See if the item itself can be swapped out.
01518         var parent = itemInfo.parent,
01519             parentItemInfo = parent ? itemInfosForItems[[parent UID]] : _rootItemInfo,
01520             parentChildren = parentItemInfo.children,
01521             index = [parentChildren indexOfObjectIdenticalTo:anItem],
01522             newItem = [dataSource outlineView:anOutlineView child:index ofItem:parent];
01523 
01524         if (anItem !== newItem)
01525         {
01526             itemInfosForItems[[anItem UID]] = nil;
01527             itemInfosForItems[[newItem UID]] = itemInfo;
01528 
01529             parentChildren[index] = newItem;
01530             _itemsForRows[itemInfo.row] = newItem;
01531         }
01532 
01533         itemInfo.isExpandable = [dataSource outlineView:anOutlineView isItemExpandable:newItem];
01534         itemInfo.isExpanded = itemInfo.isExpandable && itemInfo.isExpanded;
01535     }
01536 }
01537 
01538 // FIX ME: We're using with() here because Safari fails if we use anOutlineView._itemInfosForItems or whatever...
01539 var _loadItemInfoForItem = function(/*CPOutlineView*/ anOutlineView, /*id*/ anItem,  /*BOOL*/ isIntermediate)
01540 {
01541     with (anOutlineView)
01542     {
01543         var itemInfosForItems = _itemInfosForItems,
01544             dataSource = _outlineViewDataSource;
01545 
01546         if (!anItem)
01547             var itemInfo = _rootItemInfo;
01548 
01549         else
01550         {
01551             // Get the existing info if it exists.
01552             var itemUID = [anItem UID],
01553                 itemInfo = itemInfosForItems[itemUID];
01554 
01555             // If we're not in the tree, then just bail.
01556             if (!itemInfo)
01557                 return [];
01558 
01559             itemInfo.isExpandable = [dataSource outlineView:anOutlineView isItemExpandable:anItem];
01560 
01561             // If we were previously expanded, but now no longer expandable, "de-expand".
01562             // NOTE: we are *not* collapsing, thus no notification is posted.
01563             if (!itemInfo.isExpandable && itemInfo.isExpanded)
01564             {
01565                 itemInfo.isExpanded = NO;
01566                 itemInfo.children = [];
01567             }
01568         }
01569 
01570         // The root item does not count as a descendant.
01571         var weight = itemInfo.weight,
01572             descendants = anItem ? [anItem] : [];
01573 
01574         if (itemInfo.isExpanded && (!(_implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_shouldDeferDisplayingChildrenOfItem_) ||
01575             ![dataSource outlineView:anOutlineView shouldDeferDisplayingChildrenOfItem:anItem]))
01576         {
01577             var index = 0,
01578                 count = [dataSource outlineView:anOutlineView numberOfChildrenOfItem:anItem],
01579                 level = itemInfo.level + 1;
01580 
01581             itemInfo.children = [];
01582 
01583             for (; index < count; ++index)
01584             {
01585                 var childItem = [dataSource outlineView:anOutlineView child:index ofItem:anItem],
01586                     childItemInfo = itemInfosForItems[[childItem UID]];
01587 
01588                 if (!childItemInfo)
01589                 {
01590                     childItemInfo = { isExpanded:NO, isExpandable:NO, children:[], weight:1 };
01591                     itemInfosForItems[[childItem UID]] = childItemInfo;
01592                 }
01593 
01594                 itemInfo.children[index] = childItem;
01595 
01596                 var childDescendants = _loadItemInfoForItem(anOutlineView, childItem, YES);
01597 
01598                 childItemInfo.parent = anItem;
01599                 childItemInfo.level = level;
01600                 descendants = descendants.concat(childDescendants);
01601             }
01602         }
01603 
01604         itemInfo.weight = descendants.length;
01605 
01606         if (!isIntermediate)
01607         {
01608             // row = -1 is the root item, so just go to row 0 since it is ignored.
01609             var index = MAX(itemInfo.row, 0),
01610                 itemsForRows = _itemsForRows;
01611 
01612             descendants.unshift(index, weight);
01613 
01614             itemsForRows.splice.apply(itemsForRows, descendants);
01615 
01616             var count = itemsForRows.length;
01617 
01618             for (; index < count; ++index)
01619                 itemInfosForItems[[itemsForRows[index] UID]].row = index;
01620 
01621             var deltaWeight = itemInfo.weight - weight;
01622 
01623             if (deltaWeight !== 0)
01624             {
01625                 var parent = itemInfo.parent;
01626 
01627                 while (parent)
01628                 {
01629                     var parentItemInfo = itemInfosForItems[[parent UID]];
01630 
01631                     parentItemInfo.weight += deltaWeight;
01632                     parent = parentItemInfo.parent;
01633                 }
01634 
01635                 if (anItem)
01636                     _rootItemInfo.weight += deltaWeight;
01637             }
01638         }
01639     }//end of with
01640     return descendants;
01641 }
01642 
01643 @implementation _CPOutlineViewTableViewDataSource : CPObject
01644 {
01645     CPObject _outlineView;
01646 }
01647 
01648 - (id)initWithOutlineView:(CPOutlineView)anOutlineView
01649 {
01650     self = [super init];
01651 
01652     if (self)
01653         _outlineView = anOutlineView;
01654 
01655     return self;
01656 }
01657 
01658 - (CPInteger)numberOfRowsInTableView:(CPTableView)anOutlineView
01659 {
01660     return _outlineView._itemsForRows.length;
01661 }
01662 
01663 - (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aTableColumn row:(CPInteger)aRow
01664 {
01665     return [_outlineView._outlineViewDataSource outlineView:_outlineView objectValueForTableColumn:aTableColumn byItem:_outlineView._itemsForRows[aRow]];
01666 }
01667 
01668 - (void)tableView:(CPTableView)aTableView setObjectValue:(id)aValue forTableColumn:(CPTableColumn)aColumn row:(CPInteger)aRow
01669 {
01670     if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_setObjectValue_forTableColumn_byItem_))
01671         return;
01672     [_outlineView._outlineViewDataSource outlineView:_outlineView setObjectValue:aValue forTableColumn:aColumn byItem:_outlineView._itemsForRows[aRow]];
01673 }
01674 
01675 - (BOOL)tableView:(CPTableView)aTableColumn writeRowsWithIndexes:(CPIndexSet)theIndexes toPasteboard:(CPPasteboard)thePasteboard
01676 {
01677     if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_writeItems_toPasteboard_))
01678         return NO;
01679 
01680     var items = [],
01681         index = [theIndexes firstIndex];
01682 
01683     while (index !== CPNotFound)
01684     {
01685         [items addObject:[_outlineView itemAtRow:index]]
01686         index = [theIndexes indexGreaterThanIndex:index];
01687     }
01688 
01689     return [_outlineView._outlineViewDataSource outlineView:_outlineView writeItems:items toPasteboard:thePasteboard];
01690 }
01691 
01692 - (int)_childIndexForDropOperation:(CPTableViewDropOperation)theDropOperation row:(int)theRow offset:(CPPoint)theOffset
01693 {
01694     if (_outlineView._shouldRetargetChildIndex)
01695         return _outlineView._retargedChildIndex;
01696 
01697     var childIndex = CPNotFound;
01698 
01699     if (theDropOperation === CPTableViewDropAbove)
01700     {
01701         var parentItem = [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset],
01702             itemInfo = (parentItem !== nil) ? _outlineView._itemInfosForItems[[parentItem UID]] : _outlineView._rootItemInfo,
01703             children = itemInfo.children;
01704 
01705         childIndex = [children indexOfObject:[_outlineView itemAtRow:theRow]];
01706 
01707         if (childIndex === CPNotFound)
01708             childIndex = children.length;
01709     }
01710     else if (theDropOperation === CPTableViewDropOn)
01711         childIndex = -1;
01712 
01713     return childIndex;
01714 }
01715 
01716 - (void)_parentItemForDropOperation:(CPTableViewDropOperation)theDropOperation row:(int)theRow offset:(CPPoint)theOffset
01717 {
01718     if (theDropOperation === CPTableViewDropAbove)
01719         return [_outlineView _parentItemForUpperRow:theRow - 1 andLowerRow:theRow atMouseOffset:theOffset]
01720 
01721     return [_outlineView itemAtRow:theRow];
01722 }
01723 
01724 - (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(id < CPDraggingInfo >)theInfo
01725     proposedRow:(int)theRow proposedDropOperation:(CPTableViewDropOperation)theOperation
01726 {
01727     if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_validateDrop_proposedItem_proposedChildIndex_))
01728         return CPDragOperationNone;
01729 
01730     // Make sure the retargeted item and index are reset
01731     _outlineView._retargetedItem = nil;
01732     _outlineView._shouldRetargetItem = NO;
01733 
01734     _outlineView._retargedChildIndex = nil;
01735     _outlineView._shouldRetargetChildIndex = NO;
01736 
01737     var location = [_outlineView convertPoint:[theInfo draggingLocation] fromView:nil],
01738         parentItem = [self _parentItemForDropOperation:theOperation row:theRow offset:location],
01739         childIndex = [self _childIndexForDropOperation:theOperation row:theRow offset:location];
01740 
01741     return [_outlineView._outlineViewDataSource outlineView:_outlineView validateDrop:theInfo proposedItem:parentItem proposedChildIndex:childIndex];
01742 }
01743 
01744 - (BOOL)tableView:(CPTableView)aTableView acceptDrop:(id <CPDraggingInfo>)theInfo row:(int)theRow dropOperation:(CPTableViewDropOperation)theOperation
01745 {
01746     if (!(_outlineView._implementedOutlineViewDataSourceMethods & CPOutlineViewDataSource_outlineView_acceptDrop_item_childIndex_))
01747         return NO;
01748 
01749     var location = [_outlineView convertPoint:[theInfo draggingLocation] fromView:nil],
01750         parentItem = [self _parentItemForDropOperation:theOperation row:theRow offset:location],
01751         childIndex = [self _childIndexForDropOperation:theOperation row:theRow offset:location];
01752 
01753     _outlineView._retargetedItem = nil;
01754     _outlineView._shouldRetargetItem = NO;
01755 
01756     _outlineView._retargedChildIndex = nil;
01757     _outlineView._shouldRetargetChildIndex = NO;
01758 
01759     return [_outlineView._outlineViewDataSource outlineView:_outlineView acceptDrop:theInfo item:parentItem childIndex:childIndex];
01760 }
01761 
01762 - (void)tableView:(CPTableView)aTableView sortDescriptorsDidChange:(CPArray)oldSortDescriptors
01763 {
01764     if ((_outlineView._implementedOutlineViewDataSourceMethods &
01765          CPOutlineViewDataSource_outlineView_sortDescriptorsDidChange_))
01766     {
01767         [[_outlineView dataSource] outlineView:_outlineView sortDescriptorsDidChange:oldSortDescriptors];
01768     }
01769 }
01770 
01771 @end
01772 
01773 @implementation _CPOutlineViewTableViewDelegate : CPObject
01774 {
01775     CPOutlineView   _outlineView;
01776 }
01777 
01778 - (id)initWithOutlineView:(CPOutlineView)anOutlineView
01779 {
01780     self = [super init];
01781 
01782     if (self)
01783         _outlineView = anOutlineView;
01784 
01785     return self;
01786 }
01787 
01788 - (CPView)tableView:(CPTableView)theTableView dataViewForTableColumn:(CPTableColumn)theTableColumn row:(int)theRow
01789 {
01790     var dataView = nil;
01791 
01792     if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_dataViewForTableColumn_item_))
01793             dataView = [_outlineView._outlineViewDelegate outlineView:_outlineView
01794                                            dataViewForTableColumn:theTableColumn
01795                                                              item:[_outlineView itemAtRow:theRow]];
01796 
01797     if (!dataView)
01798         dataView = [theTableColumn dataViewForRow:theRow];
01799 
01800     return dataView;
01801 }
01802 
01803 - (BOOL)tableView:(CPTableView)theTableView shouldSelectRow:(int)theRow
01804 {
01805     if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldSelectItem_))
01806         return [_outlineView._outlineViewDelegate outlineView:_outlineView shouldSelectItem:[_outlineView itemAtRow:theRow]];
01807 
01808     return YES;
01809 }
01810 
01811 - (BOOL)tableView:(CPTableView)aTableView shouldEditTableColumn:(CPTableColumn)aColumn row:(int)aRow
01812 {
01813     if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_shouldEditTableColumn_item_))
01814         return [_outlineView._outlineViewDelegate outlineView:_outlineView shouldEditTableColumn:aColumn item:[_outlineView itemAtRow:aRow]];
01815 
01816     return NO;
01817 }
01818 
01819 - (float)tableView:(CPTableView)theTableView heightOfRow:(int)theRow
01820 {
01821     if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_heightOfRowByItem_))
01822         return [_outlineView._outlineViewDelegate outlineView:_outlineView heightOfRowByItem:[_outlineView itemAtRow:theRow]];
01823 
01824     return [theTableView rowHeight];
01825 }
01826 
01827 - (void)tableView:(CPTableView)aTableView willDisplayView:(id)aView forTableColumn:(CPTableColumn)aTableColumn row:(int)aRowIndex
01828 {
01829     if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_willDisplayView_forTableColumn_item_))
01830     {
01831         var item = [_outlineView itemAtRow:aRowIndex];
01832         [_outlineView._outlineViewDelegate outlineView:_outlineView willDisplayView:aView forTableColumn:aTableColumn item:item];
01833     }
01834 }
01835 
01836 - (BOOL)tableView:(CPTableView)aTableView isGroupRow:(int)aRow
01837 {
01838     if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_isGroupItem_))
01839         return [_outlineView._outlineViewDelegate outlineView:_outlineView isGroupItem:[_outlineView itemAtRow:aRow]];
01840 
01841     return NO;
01842 }
01843 
01844 - (CPMenu)tableView:(CPTableView)aTableView menuForTableColumn:(CPTableColumn)aTableColumn row:(int)aRow
01845 {
01846     if ((_outlineView._implementedOutlineViewDelegateMethods & CPOutlineViewDelegate_outlineView_menuForTableColumn_item_))
01847     {
01848         var item = [_outlineView itemAtRow:aRow];
01849         return [_outlineView._outlineViewDelegate outlineView:_outlineView menuForTableColumn:aTableColumn item:item]
01850     }
01851 
01852     return nil;
01853 }
01854 
01855 @end
01856 
01857 @implementation CPDisclosureButton : CPButton
01858 {
01859     float _angle;
01860 }
01861 
01862 - (id)initWithFrame:(CGRect)aFrame
01863 {
01864     self = [super initWithFrame:aFrame];
01865 
01866     if (self)
01867         [self setBordered:NO];
01868 
01869     return self;
01870 }
01871 
01872 - (void)setState:(CPState)aState
01873 {
01874     [super setState:aState];
01875 
01876     if ([self state] === CPOnState)
01877         _angle = 0.0;
01878 
01879     else
01880         _angle = -PI_2;
01881 }
01882 
01883 - (void)drawRect:(CGRect)aRect
01884 {
01885     var bounds = [self bounds],
01886         context = [[CPGraphicsContext currentContext] graphicsPort];
01887 
01888     CGContextBeginPath(context);
01889 
01890     CGContextTranslateCTM(context, _CGRectGetWidth(bounds) / 2.0, _CGRectGetHeight(bounds) / 2.0);
01891     CGContextRotateCTM(context, _angle);
01892     CGContextTranslateCTM(context, -_CGRectGetWidth(bounds) / 2.0, -_CGRectGetHeight(bounds) / 2.0);
01893 
01894     // Center, but crisp.
01895     CGContextTranslateCTM(context, FLOOR((_CGRectGetWidth(bounds) - 9.0) / 2.0), FLOOR((_CGRectGetHeight(bounds) - 8.0) / 2.0));
01896 
01897     CGContextMoveToPoint(context, 0.0, 0.0);
01898     CGContextAddLineToPoint(context, 9.0, 0.0);
01899     CGContextAddLineToPoint(context, 4.5, 8.0);
01900     CGContextAddLineToPoint(context, 0.0, 0.0);
01901 
01902     CGContextClosePath(context);
01903     CGContextSetFillColor(context,
01904         colorForDisclosureTriangle([self hasThemeState:CPThemeStateSelected],
01905             [self hasThemeState:CPThemeStateHighlighted]));
01906     CGContextFillPath(context);
01907 
01908 
01909     CGContextBeginPath(context);
01910     CGContextMoveToPoint(context, 0.0, 0.0);
01911 
01912     if (_angle === 0.0)
01913     {
01914         CGContextAddLineToPoint(context, 4.5, 8.0);
01915         CGContextAddLineToPoint(context, 9.0, 0.0);
01916     }
01917 
01918     else
01919         CGContextAddLineToPoint(context, 4.5, 8.0);
01920 
01921     CGContextSetStrokeColor(context, [CPColor colorWithCalibratedWhite:1.0 alpha: 0.8]);
01922     CGContextStrokePath(context);
01923 }
01924 
01925 @end
01926 
01927 
01928 var CPOutlineViewIndentationPerLevelKey = @"CPOutlineViewIndentationPerLevelKey",
01929     CPOutlineViewOutlineTableColumnKey = @"CPOutlineViewOutlineTableColumnKey",
01930     CPOutlineViewDataSourceKey = @"CPOutlineViewDataSourceKey",
01931     CPOutlineViewDelegateKey = @"CPOutlineViewDelegateKey";
01932 
01933 @implementation CPOutlineView (CPCoding)
01934 
01935 - (id)initWithCoder:(CPCoder)aCoder
01936 {
01937     self = [super initWithCoder:aCoder];
01938 
01939     if (self)
01940     {
01941         // The root item has weight "0", thus represents the weight solely of its descendants.
01942         _rootItemInfo = { isExpanded:YES, isExpandable:NO, level:-1, row:-1, children:[], weight:0 };
01943 
01944         _itemsForRows = [];
01945         _itemInfosForItems = { };
01946         _disclosureControlsForRows = [];
01947 
01948         [self setIndentationMarkerFollowsDataView:YES];
01949         [self setDisclosureControlPrototype:[[CPDisclosureButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 10.0, 10.0)]];
01950 
01951         _outlineTableColumn = [aCoder decodeObjectForKey:CPOutlineViewOutlineTableColumnKey];
01952         _indentationPerLevel = [aCoder decodeFloatForKey:CPOutlineViewIndentationPerLevelKey];
01953 
01954         _outlineViewDataSource = [aCoder decodeObjectForKey:CPOutlineViewDataSourceKey];
01955         _outlineViewDelegate = [aCoder decodeObjectForKey:CPOutlineViewDelegateKey];
01956 
01957         [super setDataSource:[[_CPOutlineViewTableViewDataSource alloc] initWithOutlineView:self]];
01958         [super setDelegate:[[_CPOutlineViewTableViewDelegate alloc] initWithOutlineView:self]];
01959     }
01960 
01961     return self;
01962 }
01963 
01964 - (void)encodeWithCoder:(CPCoder)aCoder
01965 {
01966     // Make sure we don't encode our internal delegate and data source.
01967     var internalDelegate = _delegate,
01968         internalDataSource = _dataSource;
01969     _delegate = nil;
01970     _dataSource = nil;
01971     [super encodeWithCoder:aCoder];
01972     _delegate = internalDelegate;
01973     _dataSource = internalDataSource;
01974 
01975     [aCoder encodeObject:_outlineTableColumn forKey:CPOutlineViewOutlineTableColumnKey];
01976     [aCoder encodeFloat:_indentationPerLevel forKey:CPOutlineViewIndentationPerLevelKey];
01977 
01978     [aCoder encodeObject:_outlineViewDataSource forKey:CPOutlineViewDataSourceKey];
01979     [aCoder encodeObject:_outlineViewDelegate forKey:CPOutlineViewDelegateKey];
01980 }
01981 
01982 @end
01983 
01984 
01985 var colorForDisclosureTriangle = function(isSelected, isHighlighted) {
01986     return isSelected
01987         ? (isHighlighted
01988             ? [CPColor colorWithCalibratedWhite:0.9 alpha: 1.0]
01989             : [CPColor colorWithCalibratedWhite:1.0 alpha: 1.0])
01990         : (isHighlighted
01991             ? [CPColor colorWithCalibratedWhite:0.4 alpha: 1.0]
01992             : [CPColor colorWithCalibratedWhite:0.5 alpha: 1.0]);
01993 }
 All Classes Files Functions Variables Defines