API 0.9.5
AppKit/CPBrowser.j
Go to the documentation of this file.
00001 /*
00002  * CPBrowser.j
00003  * AppKit
00004  *
00005  * Created by Ross Boucher.
00006  * Copyright 2010, 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 
00030 @implementation CPBrowser : CPControl
00031 {
00032     id              _delegate;
00033     CPString        _pathSeparator;
00034 
00035     CPView          _contentView;
00036     CPScrollView    _horizontalScrollView;
00037     CPView          _prototypeView;
00038 
00039     CPArray         _tableViews;
00040     CPArray         _tableDelegates;
00041 
00042     id              _rootItem;
00043 
00044     BOOL            _delegateSupportsImages;
00045 
00046     SEL             _doubleAction;
00047 
00048     BOOL            _allowsMultipleSelection;
00049     BOOL            _allowsEmptySelection;
00050 
00051     Class           _tableViewClass;
00052 
00053     float           _rowHeight;
00054     float           _imageWidth;
00055     float           _leafWidth;
00056     float           _minColumnWidth;
00057     float           _defaultColumnWidth;
00058 
00059     CPArray         _columnWidths;
00060 }
00061 
00062 + (CPImage)branchImage
00063 {
00064     return [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPBrowser class]]
00065                                                     pathForResource:"browser-leaf.png"]
00066                                               size:CGSizeMake(9,9)];
00067 }
00068 
00069 + (CPImage)highlightedBranchImage
00070 {
00071     return [[CPImage alloc] initWithContentsOfFile:[[CPBundle bundleForClass:[CPBrowser class]]
00072                                                     pathForResource:"browser-leaf-highlighted.png"]
00073                                               size:CGSizeMake(9,9)];
00074 }
00075 
00076 - (id)initWithFrame:(CGRect)aFrame
00077 {
00078     if (self = [super initWithFrame:aFrame])
00079     {
00080         [self _init];
00081     }
00082 
00083     return self;
00084 }
00085 
00086 - (void)_init
00087 {
00088     _rowHeight = 23.0;
00089     _defaultColumnWidth = 140.0;
00090     _minColumnWidth = 80.0;
00091     _imageWidth = 23.0;
00092     _leafWidth = 13.0;
00093     _columnWidths = [];
00094 
00095     _pathSeparator = "/";
00096     _tableViews = [];
00097     _tableDelegates = [];
00098     _allowsMultipleSelection = YES;
00099     _allowsEmptySelection = YES;
00100     _tableViewClass = [_CPBrowserTableView class];
00101 
00102     _prototypeView = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
00103     [_prototypeView setVerticalAlignment:CPCenterVerticalTextAlignment];
00104     [_prototypeView setValue:[CPColor whiteColor] forThemeAttribute:"text-color" inState:CPThemeStateSelectedDataView];
00105     [_prototypeView setLineBreakMode:CPLineBreakByTruncatingTail];
00106 
00107     _horizontalScrollView = [[CPScrollView alloc] initWithFrame:[self bounds]];
00108 
00109     [_horizontalScrollView setHasVerticalScroller:NO];
00110     [_horizontalScrollView setAutohidesScrollers:YES];
00111     [_horizontalScrollView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
00112 
00113     _contentView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, 0, CGRectGetHeight([self bounds]))];
00114     [_contentView setAutoresizingMask:CPViewHeightSizable];
00115 
00116     [_horizontalScrollView setDocumentView:_contentView];
00117 
00118     [self addSubview:_horizontalScrollView];
00119 }
00120 
00121 - (void)setPrototypeView:(CPView)aPrototypeView
00122 {
00123     _prototypeView = [CPKeyedUnarchiver unarchiveObjectWithData:
00124                         [CPKeyedArchiver archivedDataWithRootObject:aPrototypeView]];
00125 }
00126 
00127 - (CPView)prototypeView
00128 {
00129     return [CPKeyedUnarchiver unarchiveObjectWithData:
00130             [CPKeyedArchiver archivedDataWithRootObject:_prototypeView]];
00131 }
00132 
00133 - (void)setDelegate:(id)anObject
00134 {
00135     _delegate = anObject;
00136     _delegateSupportsImages = [_delegate respondsToSelector:@selector(browser:imageValueForItem:)];
00137 
00138     [self loadColumnZero];
00139 }
00140 
00141 - (id)delegate
00142 {
00143     return _delegate;
00144 }
00145 
00146 - (CPTableView)tableViewInColumn:(unsigned)index
00147 {
00148     return _tableViews[index];
00149 }
00150 
00151 - (unsigned)columnOfTableView:(CPTableView)aTableView
00152 {
00153     return [_tableViews indexOfObject:aTableView];
00154 }
00155 
00156 - (void)loadColumnZero
00157 {
00158     if ([_delegate respondsToSelector:@selector(rootItemForBrowser:)])
00159         _rootItem = [_delegate rootItemForBrowser:self];
00160     else
00161         _rootItem = nil;
00162 
00163     [self setLastColumn:-1];
00164     [self addColumn];
00165 }
00166 
00167 - (void)setLastColumn:(int)columnIndex
00168 {
00169     if (columnIndex >= _tableViews.length)
00170         return;
00171 
00172     var oldValue = _tableViews.length - 1,
00173         indexPlusOne = columnIndex + 1; // unloads all later columns.
00174 
00175     [[_tableViews.slice(indexPlusOne) valueForKey:"enclosingScrollView"]
00176       makeObjectsPerformSelector:@selector(removeFromSuperview)];
00177 
00178     _tableViews = _tableViews.slice(0, indexPlusOne);
00179     _tableDelegates = _tableDelegates.slice(0, indexPlusOne);
00180 
00181     if ([_delegate respondsToSelector:@selector(browser:didChangeLastColumn:toColumn:)])
00182         [_delegate browser:self didChangeLastColumn:oldValue toColumn:columnIndex];
00183 
00184     [self tile];
00185 }
00186 
00187 - (int)lastColumn
00188 {
00189     return _tableViews.length - 1;
00190 }
00191 
00192 - (void)addColumn
00193 {
00194     var lastIndex = [self lastColumn],
00195         lastColumn = _tableViews[lastIndex],
00196         selectionIndexes = [lastColumn selectedRowIndexes];
00197 
00198     if (lastIndex >= 0 && [selectionIndexes count] > 1)
00199         [CPException raise:CPInvalidArgumentException
00200                     reason:"Can't add column, column "+lastIndex+" has invalid selection."];
00201 
00202     var index = lastIndex + 1,
00203         item = index === 0 ? _rootItem : [_tableDelegates[lastIndex] childAtIndex:[selectionIndexes firstIndex]];
00204 
00205     if (index > 0 && item && [self isLeafItem:item])
00206         return;
00207 
00208     var table = [[_tableViewClass alloc] initWithFrame:CGRectMakeZero() browser:self];
00209 
00210     [table setHeaderView:nil];
00211     [table setCornerView:nil];
00212     [table setAllowsMultipleSelection:_allowsMultipleSelection];
00213     [table setAllowsEmptySelection:_allowsEmptySelection];
00214     [table registerForDraggedTypes:[self registeredDraggedTypes]];
00215 
00216     [self _addTableColumnsToTableView:table forColumnIndex:index];
00217 
00218     var delegate = [[_CPBrowserTableDelegate alloc] init];
00219 
00220     [delegate _setDelegate:_delegate];
00221     [delegate _setBrowser:self];
00222     [delegate _setIndex:index];
00223     [delegate _setItem:item];
00224 
00225     _tableViews[index] = table;
00226     _tableDelegates[index] = delegate;
00227 
00228     [table setDelegate:delegate];
00229     [table setDataSource:delegate];
00230     [table setTarget:delegate];
00231     [table setAction:@selector(_tableViewClicked:)];
00232     [table setDoubleAction:@selector(_tableViewDoubleClicked:)];
00233     [table setDraggingDestinationFeedbackStyle:CPTableViewDraggingDestinationFeedbackStyleRegular];
00234 
00235     var scrollView = [[_CPBrowserScrollView alloc] initWithFrame:CGRectMakeZero()];
00236     [scrollView _setBrowser:self];
00237     [scrollView setDocumentView:table];
00238     [scrollView setHasHorizontalScroller:NO];
00239     [scrollView setAutoresizingMask:CPViewHeightSizable];
00240 
00241     [_contentView addSubview:scrollView];
00242 
00243     [self tile];
00244 
00245     [self scrollColumnToVisible:index];
00246 }
00247 
00248 - (void)_addTableColumnsToTableView:(CPTableView)aTableView forColumnIndex:(unsigned)index
00249 {
00250     if (_delegateSupportsImages)
00251     {
00252         var column = [[CPTableColumn alloc] initWithIdentifier:@"Image"],
00253             view = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
00254 
00255         [view setImageScaling:CPScaleProportionally];
00256 
00257         [column setDataView:view];
00258         [column setResizingMask:CPTableColumnNoResizing];
00259 
00260         [aTableView addTableColumn:column];
00261     }
00262 
00263     var column = [[CPTableColumn alloc] initWithIdentifier:@"Content"];
00264 
00265     [column setDataView:_prototypeView];
00266     [column setResizingMask:CPTableColumnNoResizing];
00267 
00268     [aTableView addTableColumn:column];
00269 
00270     var column = [[CPTableColumn alloc] initWithIdentifier:@"Leaf"],
00271         view = [[_CPBrowserLeafView alloc] initWithFrame:CGRectMakeZero()];
00272 
00273     [view setBranchImage:[[self class] branchImage]];
00274     [view setHighlightedBranchImage:[[self class] highlightedBranchImage]];
00275 
00276     [column setDataView:view];
00277     [column setResizingMask:CPTableColumnNoResizing];
00278 
00279     [aTableView addTableColumn:column];
00280 }
00281 
00282 - (void)reloadColumn:(int)column
00283 {
00284     [[self tableViewInColumn:column] reloadData];
00285 }
00286 
00287 - (void)tile
00288 {
00289     var xOrigin = 0,
00290         scrollerWidth = [CPScroller scrollerWidth],
00291         height = CGRectGetHeight([_contentView bounds]);
00292 
00293     for (var i = 0, count = _tableViews.length; i < count; i++)
00294     {
00295         var tableView = _tableViews[i],
00296             scrollView = [tableView enclosingScrollView],
00297             width = [self widthOfColumn:i],
00298             tableHeight = CGRectGetHeight([tableView bounds]);
00299 
00300         [[tableView tableColumnWithIdentifier:"Image"] setWidth:_imageWidth];
00301         [[tableView tableColumnWithIdentifier:"Content"] setWidth:[self columnContentWidthForColumnWidth:width]];
00302         [[tableView tableColumnWithIdentifier:"Leaf"] setWidth:_leafWidth];
00303 
00304         [tableView setRowHeight:_rowHeight];
00305         [tableView setFrameSize:CGSizeMake(width - scrollerWidth, tableHeight)];
00306         [scrollView setFrameOrigin:CGPointMake(xOrigin, 0)];
00307         [scrollView setFrameSize:CGSizeMake(width, height)];
00308 
00309         xOrigin += width;
00310     }
00311 
00312     [_contentView setFrameSize:CGSizeMake(xOrigin, height)];
00313 }
00314 
00315 - (unsigned)rowAtPoint:(CGPoint)aPoint
00316 {
00317     var column = [self columnAtPoint:aPoint];
00318     if (column === -1)
00319         return -1;
00320 
00321     var tableView = _tableViews[column];
00322     return [tableView rowAtPoint:[tableView convertPoint:aPoint fromView:self]];
00323 }
00324 
00325 - (unsigned)columnAtPoint:(CGPoint)aPoint
00326 {
00327     var adjustedPoint = [_contentView convertPoint:aPoint fromView:self];
00328 
00329     for (var i = 0, count = _tableViews.length; i < count; i++)
00330     {
00331         var frame = [[_tableViews[i] enclosingScrollView] frame];
00332         if (CGRectContainsPoint(frame, adjustedPoint))
00333             return i;
00334     }
00335 
00336     return -1;
00337 }
00338 
00339 - (CGRect)rectOfRow:(unsigned)aRow inColumn:(unsigned)aColumn
00340 {
00341     var tableView = _tableViews[aColumn],
00342         rect = [tableView rectOfRow:aRow];
00343 
00344     rect.origin = [self convertPoint:rect.origin fromView:tableView];
00345     return rect;
00346 }
00347 
00348 // ITEMS
00349 
00350 - (id)itemAtRow:(int)row inColumn:(int)column
00351 {
00352     return [_tableDelegates[column] childAtIndex:row];
00353 }
00354 
00355 - (BOOL)isLeafItem:(id)item
00356 {
00357     return [_delegate respondsToSelector:@selector(browser:isLeafItem:)] && [_delegate browser:self isLeafItem:item];
00358 }
00359 
00360 - (id)parentForItemsInColumn:(int)column
00361 {
00362     return [_tableDelegates[column] _item];
00363 }
00364 
00365 - (CPSet)selectedItems
00366 {
00367     var selectedColumn = [self selectedColumn],
00368         selectedIndexes = [self selectedRowIndexesInColumn:selectedColumn],
00369         set = [CPSet set],
00370         index = [selectedIndexes firstIndex];
00371 
00372     while (index !== CPNotFound)
00373     {
00374         [set addObject:[self itemAtRow:index inColumn:selectedColumn]];
00375         index = [selectedIndexes indexGreaterThanIndex:index];
00376     }
00377 
00378     return set;
00379 }
00380 
00381 - (id)selectedItem
00382 {
00383     var selectedColumn = [self selectedColumn],
00384         selectedRow = [self selectedRowInColumn:selectedColumn];
00385 
00386     return [self itemAtRow:selectedRow inColumn:selectedColumn];
00387 }
00388 
00389 // CLICK EVENTS
00390 
00391 - (void)trackMouse:(CPEvent)anEvent
00392 {
00393 }
00394 
00395 - (void)_column:(unsigned)columnIndex clickedRow:(unsigned)rowIndex
00396 {
00397     [self setLastColumn:columnIndex];
00398 
00399     if (rowIndex >= 0)
00400         [self addColumn];
00401 
00402     [self doClick:self];
00403 }
00404 
00405 - (void)sendAction
00406 {
00407     [self sendAction:_action to:_target];
00408 }
00409 
00410 - (void)doClick:(id)sender
00411 {
00412     [self sendAction:_action to:_target];
00413 }
00414 
00415 - (void)doDoubleClick:(id)sender
00416 {
00417     [self sendAction:_doubleAction to:_target];
00418 }
00419 
00420 - (void)keyDown:(CPEvent)anEvent
00421 {
00422     var column = [self selectedColumn];
00423     if (column === -1)
00424         return;
00425 
00426     [_tableViews[column] keyDown:anEvent];
00427 }
00428 
00429 // SIZING
00430 
00431 - (float)columnContentWidthForColumnWidth:(float)aWidth
00432 {
00433     var columnSpacing = [_tableViews[0] intercellSpacing].width;
00434     return aWidth - (_leafWidth + columnSpacing + (_delegateSupportsImages ? _imageWidth + columnSpacing : 0)) - columnSpacing - [CPScroller scrollerWidth];
00435 }
00436 
00437 - (float)columnWidthForColumnContentWidth:(float)aWidth
00438 {
00439     var columnSpacing = [_tableViews[0] intercellSpacing].width;
00440     return aWidth + (_leafWidth + columnSpacing + (_delegateSupportsImages ? _imageWidth + columnSpacing: 0)) + columnSpacing + [CPScroller scrollerWidth];
00441 }
00442 
00443 - (void)setImageWidth:(float)aWidth
00444 {
00445     _imageWidth = aWidth;
00446     [self tile];
00447 }
00448 
00449 - (float)imageWidth
00450 {
00451     return _imageWidth;
00452 }
00453 
00454 - (void)setMinColumnWidth:(float)minWidth
00455 {
00456     _minColumnWidth = minWidth;
00457     [self tile];
00458 }
00459 
00460 - (float)minColumnWidth
00461 {
00462     return _minColumnWidth;
00463 }
00464 
00465 - (void)setWidth:(float)aWidth ofColumn:(unsigned)column
00466 {
00467     _columnWidths[column] = aWidth;
00468 
00469     if ([_delegate respondsToSelector:@selector(browser:didResizeColumn:)])
00470         [_delegate browser:self didResizeColumn:column];
00471 
00472     [self tile];
00473 }
00474 
00475 - (float)widthOfColumn:(unsigned)column
00476 {
00477     var width = _columnWidths[column];
00478 
00479     if (width == null)
00480         width = _defaultColumnWidth;
00481 
00482     return MAX([CPScroller scrollerWidth], MAX(_minColumnWidth, width));
00483 }
00484 
00485 - (void)setRowHeight:(float)aHeight
00486 {
00487     _rowHeight = aHeight;
00488 }
00489 
00490 - (float)rowHeight
00491 {
00492     return _rowHeight;
00493 }
00494 
00495 // SCROLLERS
00496 
00497 - (void)scrollColumnToVisible:(unsigned)columnIndex
00498 {
00499     [_contentView scrollRectToVisible:[[[self tableViewInColumn:columnIndex] enclosingScrollView] frame]];
00500 }
00501 
00502 - (void)scrollRowToVisible:(unsigned)rowIndex inColumn:(unsigned)columnIndex
00503 {
00504     [self scrollColumnToVisible:columnIndex];
00505     [[self tableViewInColumn:columnIndex] scrollRowToVisible:rowIndex];
00506 }
00507 
00508 - (BOOL)autohidesScroller
00509 {
00510     return [_horizontalScrollView autohidesScrollers];
00511 }
00512 
00513 - (void)setAutohidesScroller:(BOOL)shouldHide
00514 {
00515     [_horizontalScrollView setAutohidesScrollers:shouldHide];
00516 }
00517 
00518 // SELECTION
00519 
00520 - (unsigned)selectedRowInColumn:(unsigned)columnIndex
00521 {
00522     if (columnIndex > [self lastColumn] || columnIndex < 0)
00523         return -1;
00524 
00525     return [_tableViews[columnIndex] selectedRow];
00526 }
00527 
00528 - (unsigned)selectedColumn
00529 {
00530     var column = [self lastColumn],
00531         row = [self selectedRowInColumn:column];
00532 
00533     if (row >= 0)
00534         return column;
00535     else
00536         return column - 1;
00537 }
00538 
00539 - (void)selectRow:(unsigned)row inColumn:(unsigned)column
00540 {
00541     var selectedIndexes = row === -1 ? [CPIndexSet indexSet] : [CPIndexSet indexSetWithIndex:row];
00542     [self selectRowIndexes:selectedIndexes inColumn:column];
00543 }
00544 
00545 - (BOOL)allowsMultipleSelection
00546 {
00547     return _allowsMultipleSelection;
00548 }
00549 
00550 - (void)setAllowsMultipleSelection:(BOOL)shouldAllow
00551 {
00552     if (_allowsMultipleSelection === shouldAllow)
00553         return;
00554 
00555     _allowsMultipleSelection = shouldAllow;
00556     [_tableViews makeObjectsPerformSelector:@selector(setAllowsMultipleSelection:) withObject:shouldAllow];
00557 }
00558 
00559 - (BOOL)allowsEmptySelection
00560 {
00561     return _allowsEmptySelection;
00562 }
00563 
00564 - (void)setAllowsEmptySelection:(BOOL)shouldAllow
00565 {
00566     if (_allowsEmptySelection === shouldAllow)
00567         return;
00568 
00569     _allowsEmptySelection = shouldAllow;
00570     [_tableViews makeObjectsPerformSelector:@selector(setAllowsEmptySelection:) withObject:shouldAllow];
00571 }
00572 
00573 - (CPIndexSet)selectedRowIndexesInColumn:(unsigned)column
00574 {
00575     if (column < 0 || column > [self lastColumn] +1)
00576         return [CPIndexSet indexSet];
00577 
00578     return [[self tableViewInColumn:column] selectedRowIndexes];
00579 }
00580 
00581 - (void)selectRowIndexes:(CPIndexSet)indexSet inColumn:(unsigned)column
00582 {
00583     if (column < 0 || column > [self lastColumn] + 1)
00584         return;
00585 
00586     if ([_delegate respondsToSelector:@selector(browser:selectionIndexesForProposedSelection:inColumn:)])
00587         indexSet = [_delegate browser:self selectionIndexesForProposedSelection:indexSet inColumn:column];
00588 
00589     if ([_delegate respondsToSelector:@selector(browser:shouldSelectRowIndexes:inColumn:)] &&
00590        ![_delegate browser:self shouldSelectRowIndexes:indexSet inColumn:column])
00591         return;
00592 
00593     if ([_delegate respondsToSelector:@selector(browserSelectionIsChanging:)])
00594         [_delegate browserSelectionIsChanging:self];
00595 
00596     if (column > [self lastColumn])
00597         [self addColumn];
00598 
00599     [self setLastColumn:column];
00600 
00601     [[self tableViewInColumn:column] selectRowIndexes:indexSet byExtendingSelection:NO];
00602 
00603     [self scrollColumnToVisible:column];
00604 
00605     if ([_delegate respondsToSelector:@selector(browserSelectionDidChange:)])
00606         [_delegate browserSelectionDidChange:self];
00607 }
00608 
00609 - (void)setBackgroundColor:(CPColor)aColor
00610 {
00611     [super setBackgroundColor:aColor];
00612     [_contentView setBackgroundColor:aColor];
00613 }
00614 
00615 - (BOOL)acceptsFirstResponder
00616 {
00617     return YES;
00618 }
00619 
00620 // DRAG AND DROP
00621 
00622 - (void)registerForDraggedTypes:(CPArray)types
00623 {
00624     [super registerForDraggedTypes:types];
00625     [_tableViews makeObjectsPerformSelector:@selector(registerForDraggedTypes:) withObject:types];
00626 }
00627 
00628 - (BOOL)canDragRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(int)columnIndex withEvent:(CPEvent)dragEvent
00629 {
00630     if ([_delegate respondsToSelector:@selector(browser:canDragRowsWithIndexes:inColumn:withEvent:)])
00631         return [_delegate browser:self canDragRowsWithIndexes:rowIndexes inColumn:columnIndex withEvent:dragEvent];
00632 
00633     return YES;
00634 }
00635 
00636 - (CPImage)draggingImageForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(int)columnIndex withEvent:(CPEvent)dragEvent offset:(CGPoint)dragImageOffset
00637 {
00638     if ([_delegate respondsToSelector:@selector(browser:draggingImageForRowsWithIndexes:inColumn:withEvent:offset:)])
00639         return [_delegate browser:self draggingImageForRowsWithIndexes:rowIndexes inColumn:columnIndex withEvent:dragEvent offset:dragImageOffset];
00640 
00641     return nil;
00642 }
00643 
00644 - (CPView)draggingViewForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(int)columnIndex withEvent:(CPEvent)dragEvent offset:(CGPoint)dragImageOffset
00645 {
00646     if ([_delegate respondsToSelector:@selector(browser:draggingViewForRowsWithIndexes:inColumn:withEvent:offset:)])
00647         return [_delegate browser:self draggingViewForRowsWithIndexes:rowIndexes inColumn:columnIndex withEvent:dragEvent offset:dragImageOffset];
00648 
00649     return nil;
00650 }
00651 
00652 @end
00653 
00654 @implementation CPBrowser (CPCoding)
00655 
00656 - (id)initWithCoder:(CPCoder)aCoder
00657 {
00658     self = [super initWithCoder:aCoder];
00659 
00660     if (self)
00661     {
00662         [self _init];
00663 
00664         _allowsEmptySelection = [aCoder decodeBoolForKey:@"CPBrowserAllowsEmptySelectionKey"];
00665         _allowsMultipleSelection = [aCoder decodeBoolForKey:@"CPBrowserAllowsMultipleSelectionKey"];
00666         _prototypeView = [aCoder decodeObjectForKey:@"CPBrowserPrototypeViewKey"];
00667         _rowHeight = [aCoder decodeFloatForKey:@"CPBrowserRowHeightKey"];
00668         _imageWidth = [aCoder decodeFloatForKey:@"CPBrowserImageWidthKey"];
00669         _minColumnWidth = [aCoder decodeFloatForKey:@"CPBrowserMinColumnWidthKey"];
00670         _columnWidths = [aCoder decodeObjectForKey:@"CPBrowserColumnWidthsKey"];
00671 
00672         [self setDelegate:[aCoder decodeObjectForKey:@"CPBrowserDelegateKey"]];
00673         [self setAutohidesScroller:[aCoder decodeBoolForKey:@"CPBrowserAutohidesScrollerKey"]];
00674     }
00675 
00676     return self;
00677 }
00678 
00679 - (void)encodeWithCoder:(CPCoder)aCoder
00680 {
00681     // Don't encode the subviews, they're transient and will be recreated from data.
00682     var actualSubviews = _subviews;
00683     _subviews = [];
00684     [super encodeWithCoder:aCoder];
00685     _subviews = actualSubviews;
00686 
00687     [aCoder encodeBool:[self autohidesScroller] forKey:@"CPBrowserAutohidesScrollerKey"];
00688     [aCoder encodeBool:_allowsEmptySelection forKey:@"CPBrowserAllowsEmptySelectionKey"];
00689     [aCoder encodeBool:_allowsMultipleSelection forKey:@"CPBrowserAllowsMultipleSelectionKey"];
00690     [aCoder encodeObject:_delegate forKey:@"CPBrowserDelegateKey"];
00691     [aCoder encodeObject:_prototypeView forKey:@"CPBrowserPrototypeViewKey"];
00692     [aCoder encodeFloat:_rowHeight forKey:@"CPBrowserRowHeightKey"];
00693     [aCoder encodeFloat:_imageWidth forKey:@"CPBrowserImageWidthKey"];
00694     [aCoder encodeFloat:_minColumnWidth forKey:@"CPBrowserMinColumnWidthKey"];
00695     [aCoder encodeObject:_columnWidths forKey:@"CPBrowserColumnWidthsKey"];
00696 }
00697 
00698 @end
00699 
00700 
00701 var _CPBrowserResizeControlBackgroundImage = nil;
00702 
00703 @implementation _CPBrowserResizeControl : CPView
00704 {
00705     CGPoint     _mouseDownX;
00706     CPBrowser   _browser;
00707     unsigned    _index;
00708     unsigned    _width;
00709 }
00710 
00711 + (CPImage)backgroundImage
00712 {
00713     if (!_CPBrowserResizeControlBackgroundImage)
00714     {
00715         var path = [[CPBundle bundleForClass:[self class]] pathForResource:"browser-resize-control.png"];
00716         _CPBrowserResizeControlBackgroundImage = [[CPImage alloc] initWithContentsOfFile:path
00717                                                                                     size:CGSizeMake(15, 14)];
00718     }
00719 
00720     return _CPBrowserResizeControlBackgroundImage;
00721 }
00722 
00723 - (id)initWithFrame:(CGRect)aFrame
00724 {
00725     if (self = [super initWithFrame:aFrame])
00726         [self setBackgroundColor:[CPColor colorWithPatternImage:[[self class] backgroundImage]]];
00727 
00728     return self;
00729 }
00730 
00731 - (void)mouseDown:(CPEvent)anEvent
00732 {
00733     _mouseDownX = [anEvent locationInWindow].x;
00734     _browser = [[self superview] _browser];
00735     _index = [_browser columnOfTableView:[[self superview] documentView]];
00736     _width = [_browser widthOfColumn:_index];
00737 }
00738 
00739 - (void)mouseDragged:(CPEvent)anEvent
00740 {
00741     var deltaX = [anEvent locationInWindow].x - _mouseDownX;
00742     [_browser setWidth:_width + deltaX ofColumn:_index];
00743 }
00744 
00745 - (void)mouseUp:(CPEvent)anEvent
00746 {
00747 }
00748 
00749 @end
00750 
00751 @implementation _CPBrowserScrollView : CPScrollView
00752 {
00753     _CPBrowserResizeControl  _resizeControl;
00754     CPBrowser                _browser;
00755 }
00756 
00757 - (void)initWithFrame:(CGRect)aFrame
00758 {
00759     if (self = [super initWithFrame:aFrame])
00760     {
00761         _resizeControl = [[_CPBrowserResizeControl alloc] initWithFrame:CGRectMakeZero()];
00762         [self addSubview:_resizeControl];
00763     }
00764 
00765     return self;
00766 }
00767 
00768 - (void)reflectScrolledClipView:(CPClipView)aClipView
00769 {
00770     [super reflectScrolledClipView:aClipView];
00771 
00772     var frame = [_verticalScroller frame];
00773     frame.size.height = CGRectGetHeight([self bounds]) - 14.0 - frame.origin.y;
00774     [_verticalScroller setFrameSize:frame.size];
00775 
00776     var resizeFrame = CGRectMake(CGRectGetMinX(frame), CGRectGetMaxY(frame), [CPScroller scrollerWidth], 14.0);
00777     [_resizeControl setFrame:resizeFrame];
00778 }
00779 
00780 @end
00781 
00782 @implementation _CPBrowserTableView : CPTableView
00783 {
00784     CPBrowser   _browser;
00785 }
00786 
00787 - (id)initWithFrame:(CGRect)aFrame browser:(CPBrowser)aBrowser
00788 {
00789     if (self = [super initWithFrame:aFrame])
00790         _browser = aBrowser;
00791 
00792     return self;
00793 }
00794 
00795 - (BOOL)acceptsFirstResponder
00796 {
00797     return NO;
00798 }
00799 
00800 - (void)mouseDown:(CPEvent)anEvent
00801 {
00802     [super mouseDown:anEvent];
00803     [[self window] makeFirstResponder:_browser];
00804 }
00805 
00806 - (CPView)browserView
00807 {
00808     return _browser;
00809 }
00810 
00811 - (BOOL)canDragRowsWithIndexes:(CPIndexSet)rowIndexes atPoint:(CGPoint)mouseDownPoint
00812 {
00813     return [_browser canDragRowsWithIndexes:rowIndexes inColumn:[_browser columnOfTableView:self] withEvent:[CPApp currentEvent]];
00814 }
00815 
00816 - (CPImage)dragImageForRowsWithIndexes:(CPIndexSet)dragRows tableColumns:(CPArray)theTableColumns event:(CPEvent)dragEvent offset:(CPPointPointer)dragImageOffset
00817 {
00818     return [_browser draggingImageForRowsWithIndexes:dragRows inColumn:[_browser columnOfTableView:self] withEvent:dragEvent offset:dragImageOffset] ||
00819            [super dragImageForRowsWithIndexes:dragRows tableColumns:theTableColumns event:dragEvent offset:dragImageOffset];
00820 }
00821 
00822 - (CPView)dragViewForRowsWithIndexes:(CPIndexSet)dragRows tableColumns:(CPArray)theTableColumns event:(CPEvent)dragEvent offset:(CPPoint)dragViewOffset
00823 {
00824     var count = theTableColumns.length;
00825     while (count--)
00826     {
00827         if ([theTableColumns[count] identifier] === "Leaf")
00828             [theTableColumns removeObject:theTableColumns[count]];
00829     }
00830 
00831     return [_browser draggingViewForRowsWithIndexes:dragRows inColumn:[_browser columnOfTableView:self] withEvent:dragEvent offset:dragViewOffset] ||
00832            [super dragViewForRowsWithIndexes:dragRows tableColumns:theTableColumns event:dragEvent offset:dragViewOffset];
00833 }
00834 
00835 - (void)moveUp:(id)sender
00836 {
00837     [super moveUp:sender];
00838     [_browser selectRow:[self selectedRow] inColumn:[_browser selectedColumn]];
00839 }
00840 
00841 - (void)moveDown:(id)sender
00842 {
00843     [super moveDown:sender];
00844     [_browser selectRow:[self selectedRow] inColumn:[_browser selectedColumn]];
00845 }
00846 
00847 - (void)moveLeft:(id)sender
00848 {
00849     var previousColumn = [_browser selectedColumn] - 1,
00850         selectedRow = [_browser selectedRowInColumn:previousColumn];
00851 
00852     [_browser selectRow:selectedRow inColumn:previousColumn];
00853 }
00854 
00855 - (void)moveRight:(id)sender
00856 {
00857     [_browser selectRow:0 inColumn:[_browser selectedColumn] + 1];
00858 }
00859 
00860 @end
00861 
00862 
00863 @implementation _CPBrowserTableDelegate : CPObject
00864 {
00865     CPBrowser   _browser;
00866     unsigned    _index;
00867     id          _delegate;
00868     id          _item;
00869 }
00870 
00871 - (unsigned)numberOfRowsInTableView:(CPTableView)aTableView
00872 {
00873     return [_delegate browser:_browser numberOfChildrenOfItem:_item];
00874 }
00875 
00876 - (void)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)column row:(unsigned)row
00877 {
00878     if ([column identifier] === "Image")
00879         return [_delegate browser:_browser imageValueForItem:[self childAtIndex:row]];
00880     else if ([column identifier] === "Leaf")
00881         return ![_browser isLeafItem:[self childAtIndex:row]];
00882     else
00883         return [_delegate browser:_browser objectValueForItem:[self childAtIndex:row]];
00884 }
00885 
00886 - (void)_tableViewDoubleClicked:(CPTableView)aTableView
00887 {
00888     [_browser doDoubleClick:self];
00889 }
00890 
00891 - (void)_tableViewClicked:(CPTableView)aTableView
00892 {
00893     var selectedIndexes = [aTableView selectedRowIndexes];
00894     [_browser _column:_index clickedRow:[selectedIndexes count] === 1 ? [selectedIndexes firstIndex] : -1];
00895 }
00896 
00897 - (id)childAtIndex:(unsigned)index
00898 {
00899     return [_delegate browser:_browser child:index ofItem:_item];
00900 }
00901 
00902 - (BOOL)tableView:(CPTableView)aTableView acceptDrop:(id)info row:(int)row dropOperation:(CPTableViewDropOperation)operation
00903 {
00904     if ([_delegate respondsToSelector:@selector(browser:acceptDrop:atRow:column:dropOperation:)])
00905         return [_delegate browser:_browser acceptDrop:info atRow:row column:_index dropOperation:operation];
00906     else
00907         return NO;
00908 }
00909 
00910 - (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(id)info proposedRow:(int)row proposedDropOperation:(CPTableViewDropOperation)operation
00911 {
00912     if ([_delegate respondsToSelector:@selector(browser:validateDrop:proposedRow:column:dropOperation:)])
00913         return [_delegate browser:_browser validateDrop:info proposedRow:row column:_index dropOperation:operation];
00914     else
00915         return CPDragOperationNone;
00916 }
00917 
00918 - (BOOL)tableView:(CPTableView)aTableView writeRowsWithIndexes:(CPIndexSet)rowIndexes toPasteboard:(CPPasteboard)pboard
00919 {
00920     if ([_delegate respondsToSelector:@selector(browser:writeRowsWithIndexes:inColumn:toPasteboard:)])
00921         return [_delegate browser:_browser writeRowsWithIndexes:rowIndexes inColumn:_index toPasteboard:pboard];
00922     else
00923         return NO;
00924 }
00925 
00926 - (BOOL)respondsToSelector:(SEL)aSelector
00927 {
00928     if (aSelector === @selector(browser:writeRowsWithIndexes:inColumn:toPasteboard:))
00929         return [_delegate respondsToSelector:@selector(browser:writeRowsWithIndexes:inColumn:toPasteboard:)];
00930     else
00931         return [super respondsToSelector:aSelector];
00932 }
00933 
00934 @end
00935 
00936 @implementation _CPBrowserLeafView : CPView
00937 {
00938     BOOL        _isLeaf;
00939     CPImage     _branchImage;
00940     CPImage     _highlightedBranchImage;
00941 }
00942 
00943 - (BOOL)objectValue
00944 {
00945     return _isLeaf;
00946 }
00947 
00948 - (void)setObjectValue:(id)aValue
00949 {
00950     _isLeaf = !!aValue;
00951     [self setNeedsLayout];
00952 }
00953 
00954 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
00955 {
00956     if (aName === "image-view")
00957         return CGRectInset([self bounds], 1, 1);
00958 
00959     return [super rectForEphemeralSubviewNamed:aName];
00960 }
00961 
00962 - (CPView)createEphemeralSubviewNamed:(CPString)aName
00963 {
00964     if (aName === "image-view")
00965         return [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
00966 
00967     return [super createEphemeralSubviewNamed:aName];
00968 }
00969 
00970 - (void)layoutSubviews
00971 {
00972     var imageView = [self layoutEphemeralSubviewNamed:@"image-view"
00973                                            positioned:CPWindowAbove
00974                       relativeToEphemeralSubviewNamed:nil],
00975         isHighlighted = [self themeState] & CPThemeStateSelectedDataView;
00976 
00977     [imageView setImage: _isLeaf ? (isHighlighted ? _highlightedBranchImage : _branchImage) : nil];
00978     [imageView setImageScaling:CPScaleNone];
00979 }
00980 
00981 - (void)encodeWithCoder:(CPCoder)aCoder
00982 {
00983     [super encodeWithCoder:aCoder];
00984 
00985     [aCoder encodeBool:_isLeaf forKey:"_CPBrowserLeafViewIsLeafKey"];
00986     [aCoder encodeObject:_branchImage forKey:"_CPBrowserLeafViewBranchImageKey"];
00987     [aCoder encodeObject:_highlightedBranchImage forKey:"_CPBrowserLeafViewHighlightedBranchImageKey"];
00988 }
00989 
00990 - (void)initWithCoder:(CPCoder)aCoder
00991 {
00992     if (self = [super initWithCoder:aCoder])
00993     {
00994         _isLeaf = [aCoder decodeBoolForKey:"_CPBrowserLeafViewIsLeafKey"];
00995         _branchImage = [aCoder decodeObjectForKey:"_CPBrowserLeafViewBranchImageKey"];
00996         _highlightedBranchImage = [aCoder decodeObjectForKey:"_CPBrowserLeafViewHighlightedBranchImageKey"];
00997     }
00998 
00999     return self;
01000 }
01001 
01002 @end
01003 
01004 @implementation CPBrowser (CPSynthesizedAccessors)
01005 
01009 - (SEL)doubleAction
01010 {
01011     return _doubleAction;
01012 }
01013 
01017 - (void)setDoubleAction:(SEL)aValue
01018 {
01019     _doubleAction = aValue;
01020 }
01021 
01025 - (Class)tableViewClass
01026 {
01027     return _tableViewClass;
01028 }
01029 
01033 - (void)setTableViewClass:(Class)aValue
01034 {
01035     _tableViewClass = aValue;
01036 }
01037 
01041 - (float)defaultColumnWidth
01042 {
01043     return _defaultColumnWidth;
01044 }
01045 
01049 - (void)setDefaultColumnWidth:(float)aValue
01050 {
01051     _defaultColumnWidth = aValue;
01052 }
01053 
01054 @end
 All Classes Files Functions Variables Defines