API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPBrowser.j
Go to the documentation of this file.
1 /*
2  * CPBrowser.j
3  * AppKit
4  *
5  * Created by Ross Boucher.
6  * Copyright 2010, 280 North, Inc.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21  */
22 
23 
24 
30 @implementation CPBrowser : CPControl
31 {
32  id _delegate;
33  CPString _pathSeparator;
34 
35  CPView _contentView;
36  CPScrollView _horizontalScrollView;
37  CPView _prototypeView;
38 
39  CPArray _tableViews;
40  CPArray _tableDelegates;
41 
42  id _rootItem;
43 
44  BOOL _delegateSupportsImages;
45 
46  SEL _doubleAction;
47 
48  BOOL _allowsMultipleSelection;
49  BOOL _allowsEmptySelection;
50 
51  Class _tableViewClass;
52 
53  float _rowHeight;
54  float _imageWidth;
55  float _leafWidth;
56  float _minColumnWidth;
57  float _defaultColumnWidth;
58 
59  CPArray _columnWidths;
60 }
61 
62 + (CPImage)branchImage
63 {
65  pathForResource:"browser-leaf.png"]
66  size:CGSizeMake(9,9)];
67 }
68 
69 + (CPImage)highlightedBranchImage
70 {
72  pathForResource:"browser-leaf-highlighted.png"]
73  size:CGSizeMake(9,9)];
74 }
75 
76 - (id)initWithFrame:(CGRect)aFrame
77 {
78  if (self = [super initWithFrame:aFrame])
79  {
80  [self _init];
81  }
82 
83  return self;
84 }
85 
86 - (void)_init
87 {
88  _rowHeight = 23.0;
89  _defaultColumnWidth = 140.0;
90  _minColumnWidth = 80.0;
91  _imageWidth = 23.0;
92  _leafWidth = 13.0;
93  _columnWidths = [];
94 
95  _pathSeparator = "/";
96  _tableViews = [];
97  _tableDelegates = [];
98  _allowsMultipleSelection = YES;
99  _allowsEmptySelection = YES;
100  _tableViewClass = [_CPBrowserTableView class];
101 
102  _prototypeView = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
103  [_prototypeView setVerticalAlignment:CPCenterVerticalTextAlignment];
104  [_prototypeView setValue:[CPColor whiteColor] forThemeAttribute:"text-color" inState:CPThemeStateSelectedDataView];
105  [_prototypeView setLineBreakMode:CPLineBreakByTruncatingTail];
106 
107  _horizontalScrollView = [[CPScrollView alloc] initWithFrame:[self bounds]];
108 
109  [_horizontalScrollView setHasVerticalScroller:NO];
110  [_horizontalScrollView setAutohidesScrollers:YES];
111  [_horizontalScrollView setAutoresizingMask:CPViewWidthSizable | CPViewHeightSizable];
112 
113  _contentView = [[CPView alloc] initWithFrame:CGRectMake(0, 0, 0, CGRectGetHeight([self bounds]))];
114  [_contentView setAutoresizingMask:CPViewHeightSizable];
115 
116  [_horizontalScrollView setDocumentView:_contentView];
117 
118  [self addSubview:_horizontalScrollView];
119 }
120 
121 - (void)setPrototypeView:(CPView)aPrototypeView
122 {
123  _prototypeView = [CPKeyedUnarchiver unarchiveObjectWithData:
125 }
126 
127 - (CPView)prototypeView
128 {
131 }
132 
133 - (void)setDelegate:(id)anObject
134 {
135  _delegate = anObject;
136  _delegateSupportsImages = [_delegate respondsToSelector:@selector(browser:imageValueForItem:)];
137 
138  [self loadColumnZero];
139 }
140 
141 - (id)delegate
142 {
143  return _delegate;
144 }
145 
146 - (CPTableView)tableViewInColumn:(unsigned)index
147 {
148  return _tableViews[index];
149 }
150 
151 - (unsigned)columnOfTableView:(CPTableView)aTableView
152 {
153  return [_tableViews indexOfObject:aTableView];
154 }
155 
156 - (void)loadColumnZero
157 {
158  if ([_delegate respondsToSelector:@selector(rootItemForBrowser:)])
159  _rootItem = [_delegate rootItemForBrowser:self];
160  else
161  _rootItem = nil;
162 
163  [self setLastColumn:-1];
164  [self addColumn];
165 }
166 
167 - (void)setLastColumn:(int)columnIndex
168 {
169  if (columnIndex >= _tableViews.length)
170  return;
171 
172  var oldValue = _tableViews.length - 1,
173  indexPlusOne = columnIndex + 1; // unloads all later columns.
174 
175  [[_tableViews.slice(indexPlusOne) valueForKey:"enclosingScrollView"]
176  makeObjectsPerformSelector:@selector(removeFromSuperview)];
177 
178  _tableViews = _tableViews.slice(0, indexPlusOne);
179  _tableDelegates = _tableDelegates.slice(0, indexPlusOne);
180 
181  if ([_delegate respondsToSelector:@selector(browser:didChangeLastColumn:toColumn:)])
182  [_delegate browser:self didChangeLastColumn:oldValue toColumn:columnIndex];
183 
184  [self tile];
185 }
186 
187 - (int)lastColumn
188 {
189  return _tableViews.length - 1;
190 }
191 
192 - (void)addColumn
193 {
194  var lastIndex = [self lastColumn],
195  lastColumn = _tableViews[lastIndex],
196  selectionIndexes = [lastColumn selectedRowIndexes];
197 
198  if (lastIndex >= 0 && [selectionIndexes count] > 1)
199  [CPException raise:CPInvalidArgumentException
200  reason:"Can't add column, column "+lastIndex+" has invalid selection."];
201 
202  var index = lastIndex + 1,
203  item = index === 0 ? _rootItem : [_tableDelegates[lastIndex] childAtIndex:[selectionIndexes firstIndex]];
204 
205  if (index > 0 && item && [self isLeafItem:item])
206  return;
207 
208  var table = [[_tableViewClass alloc] initWithFrame:CGRectMakeZero() browser:self];
209 
210  [table setHeaderView:nil];
211  [table setCornerView:nil];
212  [table setAllowsMultipleSelection:_allowsMultipleSelection];
213  [table setAllowsEmptySelection:_allowsEmptySelection];
214  [table registerForDraggedTypes:[self registeredDraggedTypes]];
215 
216  [self _addTableColumnsToTableView:table forColumnIndex:index];
217 
218  var delegate = [[_CPBrowserTableDelegate alloc] init];
219 
220  [delegate _setDelegate:_delegate];
221  [delegate _setBrowser:self];
222  [delegate _setIndex:index];
223  [delegate _setItem:item];
224 
225  _tableViews[index] = table;
226  _tableDelegates[index] = delegate;
227 
228  [table setDelegate:delegate];
229  [table setDataSource:delegate];
230  [table setTarget:delegate];
231  [table setAction:@selector(_tableViewClicked:)];
232  [table setDoubleAction:@selector(_tableViewDoubleClicked:)];
233  [table setDraggingDestinationFeedbackStyle:CPTableViewDraggingDestinationFeedbackStyleRegular];
234 
235  var scrollView = [[_CPBrowserScrollView alloc] initWithFrame:CGRectMakeZero()];
236  [scrollView _setBrowser:self];
237  [scrollView setDocumentView:table];
238  [scrollView setHasHorizontalScroller:NO];
239  [scrollView setAutoresizingMask:CPViewHeightSizable];
240 
241  [_contentView addSubview:scrollView];
242 
243  [self tile];
244 
245  [self scrollColumnToVisible:index];
246 }
247 
248 - (void)_addTableColumnsToTableView:(CPTableView)aTableView forColumnIndex:(unsigned)index
249 {
250  if (_delegateSupportsImages)
251  {
252  var column = [[CPTableColumn alloc] initWithIdentifier:@"Image"],
253  view = [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
254 
255  [view setImageScaling:CPImageScaleProportionallyDown];
256 
257  [column setDataView:view];
258  [column setResizingMask:CPTableColumnNoResizing];
259 
260  [aTableView addTableColumn:column];
261  }
262 
263  var column = [[CPTableColumn alloc] initWithIdentifier:@"Content"];
264 
265  [column setDataView:_prototypeView];
266  [column setResizingMask:CPTableColumnNoResizing];
267 
268  [aTableView addTableColumn:column];
269 
270  var column = [[CPTableColumn alloc] initWithIdentifier:@"Leaf"],
271  view = [[_CPBrowserLeafView alloc] initWithFrame:CGRectMakeZero()];
272 
273  [view setBranchImage:[[self class] branchImage]];
274  [view setHighlightedBranchImage:[[self class] highlightedBranchImage]];
275 
276  [column setDataView:view];
277  [column setResizingMask:CPTableColumnNoResizing];
278 
279  [aTableView addTableColumn:column];
280 }
281 
282 - (void)reloadColumn:(int)column
283 {
284  [[self tableViewInColumn:column] reloadData];
285 }
286 
287 - (void)tile
288 {
289  var xOrigin = 0,
290  scrollerWidth = [CPScroller scrollerWidth],
291  height = CGRectGetHeight([_contentView bounds]);
292 
293  for (var i = 0, count = _tableViews.length; i < count; i++)
294  {
295  var tableView = _tableViews[i],
296  scrollView = [tableView enclosingScrollView],
297  width = [self widthOfColumn:i],
298  tableHeight = CGRectGetHeight([tableView bounds]);
299 
300  [[tableView tableColumnWithIdentifier:"Image"] setWidth:_imageWidth];
301  [[tableView tableColumnWithIdentifier:"Content"] setWidth:[self columnContentWidthForColumnWidth:width]];
302  [[tableView tableColumnWithIdentifier:"Leaf"] setWidth:_leafWidth];
303 
304  [tableView setRowHeight:_rowHeight];
305  [tableView setFrameSize:CGSizeMake(width - scrollerWidth, tableHeight)];
306  [scrollView setFrameOrigin:CGPointMake(xOrigin, 0)];
307  [scrollView setFrameSize:CGSizeMake(width, height)];
308 
309  xOrigin += width;
310  }
311 
312  [_contentView setFrameSize:CGSizeMake(xOrigin, height)];
313 }
314 
315 - (unsigned)rowAtPoint:(CGPoint)aPoint
316 {
317  var column = [self columnAtPoint:aPoint];
318  if (column === -1)
319  return -1;
320 
321  var tableView = _tableViews[column];
322  return [tableView rowAtPoint:[tableView convertPoint:aPoint fromView:self]];
323 }
324 
325 - (unsigned)columnAtPoint:(CGPoint)aPoint
326 {
327  var adjustedPoint = [_contentView convertPoint:aPoint fromView:self];
328 
329  for (var i = 0, count = _tableViews.length; i < count; i++)
330  {
331  var frame = [[_tableViews[i] enclosingScrollView] frame];
332  if (CGRectContainsPoint(frame, adjustedPoint))
333  return i;
334  }
335 
336  return -1;
337 }
338 
339 - (CGRect)rectOfRow:(unsigned)aRow inColumn:(unsigned)aColumn
340 {
341  var tableView = _tableViews[aColumn],
342  rect = [tableView rectOfRow:aRow];
343 
344  rect.origin = [self convertPoint:rect.origin fromView:tableView];
345  return rect;
346 }
347 
348 // ITEMS
349 
350 - (id)itemAtRow:(int)row inColumn:(int)column
351 {
352  return [_tableDelegates[column] childAtIndex:row];
353 }
354 
355 - (BOOL)isLeafItem:(id)item
356 {
357  return [_delegate respondsToSelector:@selector(browser:isLeafItem:)] && [_delegate browser:self isLeafItem:item];
358 }
359 
360 - (id)parentForItemsInColumn:(int)column
361 {
362  return [_tableDelegates[column] _item];
363 }
364 
365 - (CPSet)selectedItems
366 {
367  var selectedColumn = [self selectedColumn],
368  selectedIndexes = [self selectedRowIndexesInColumn:selectedColumn],
369  set = [CPSet set],
370  index = [selectedIndexes firstIndex];
371 
372  while (index !== CPNotFound)
373  {
374  [set addObject:[self itemAtRow:index inColumn:selectedColumn]];
375  index = [selectedIndexes indexGreaterThanIndex:index];
376  }
377 
378  return set;
379 }
380 
381 - (id)selectedItem
382 {
383  var selectedColumn = [self selectedColumn],
384  selectedRow = [self selectedRowInColumn:selectedColumn];
385 
386  return [self itemAtRow:selectedRow inColumn:selectedColumn];
387 }
388 
389 // CLICK EVENTS
390 
391 - (void)trackMouse:(CPEvent)anEvent
392 {
393 }
394 
395 - (void)_column:(unsigned)columnIndex clickedRow:(unsigned)rowIndex
396 {
397  [self setLastColumn:columnIndex];
398 
399  if (rowIndex >= 0)
400  [self addColumn];
401 
402  [self doClick:self];
403 }
404 
405 - (void)sendAction
406 {
407  [self sendAction:_action to:_target];
408 }
409 
410 - (void)doClick:(id)sender
411 {
412  [self sendAction:_action to:_target];
413 }
414 
415 - (void)doDoubleClick:(id)sender
416 {
417  [self sendAction:_doubleAction to:_target];
418 }
419 
420 - (void)keyDown:(CPEvent)anEvent
421 {
422  var column = [self selectedColumn];
423  if (column === -1)
424  return;
425 
426  [_tableViews[column] keyDown:anEvent];
427 }
428 
429 // SIZING
430 
431 - (float)columnContentWidthForColumnWidth:(float)aWidth
432 {
433  var columnSpacing = [_tableViews[0] intercellSpacing].width;
434  return aWidth - (_leafWidth + columnSpacing + (_delegateSupportsImages ? _imageWidth + columnSpacing : 0)) - columnSpacing - [CPScroller scrollerWidth];
435 }
436 
437 - (float)columnWidthForColumnContentWidth:(float)aWidth
438 {
439  var columnSpacing = [_tableViews[0] intercellSpacing].width;
440  return aWidth + (_leafWidth + columnSpacing + (_delegateSupportsImages ? _imageWidth + columnSpacing: 0)) + columnSpacing + [CPScroller scrollerWidth];
441 }
442 
443 - (void)setImageWidth:(float)aWidth
444 {
445  _imageWidth = aWidth;
446  [self tile];
447 }
448 
449 - (float)imageWidth
450 {
451  return _imageWidth;
452 }
453 
454 - (void)setMinColumnWidth:(float)minWidth
455 {
456  _minColumnWidth = minWidth;
457  [self tile];
458 }
459 
460 - (float)minColumnWidth
461 {
462  return _minColumnWidth;
463 }
464 
465 - (void)setWidth:(float)aWidth ofColumn:(unsigned)column
466 {
467  _columnWidths[column] = aWidth;
468 
469  if ([_delegate respondsToSelector:@selector(browser:didResizeColumn:)])
470  [_delegate browser:self didResizeColumn:column];
471 
472  [self tile];
473 }
474 
475 - (float)widthOfColumn:(unsigned)column
476 {
477  var width = _columnWidths[column];
478 
479  if (width == null)
480  width = _defaultColumnWidth;
481 
482  return MAX([CPScroller scrollerWidth], MAX(_minColumnWidth, width));
483 }
484 
485 - (void)setRowHeight:(float)aHeight
486 {
487  _rowHeight = aHeight;
488 }
489 
490 - (float)rowHeight
491 {
492  return _rowHeight;
493 }
494 
495 // SCROLLERS
496 
497 - (void)scrollColumnToVisible:(unsigned)columnIndex
498 {
499  [_contentView scrollRectToVisible:[[[self tableViewInColumn:columnIndex] enclosingScrollView] frame]];
500 }
501 
502 - (void)scrollRowToVisible:(unsigned)rowIndex inColumn:(unsigned)columnIndex
503 {
504  [self scrollColumnToVisible:columnIndex];
505  [[self tableViewInColumn:columnIndex] scrollRowToVisible:rowIndex];
506 }
507 
508 - (BOOL)autohidesScroller
509 {
510  return [_horizontalScrollView autohidesScrollers];
511 }
512 
513 - (void)setAutohidesScroller:(BOOL)shouldHide
514 {
515  [_horizontalScrollView setAutohidesScrollers:shouldHide];
516 }
517 
518 // SELECTION
519 
520 - (unsigned)selectedRowInColumn:(unsigned)columnIndex
521 {
522  if (columnIndex > [self lastColumn] || columnIndex < 0)
523  return -1;
524 
525  return [_tableViews[columnIndex] selectedRow];
526 }
527 
528 - (unsigned)selectedColumn
529 {
530  var column = [self lastColumn],
531  row = [self selectedRowInColumn:column];
532 
533  if (row >= 0)
534  return column;
535  else
536  return column - 1;
537 }
538 
539 - (void)selectRow:(unsigned)row inColumn:(unsigned)column
540 {
541  var selectedIndexes = row === -1 ? [CPIndexSet indexSet] : [CPIndexSet indexSetWithIndex:row];
542  [self selectRowIndexes:selectedIndexes inColumn:column];
543 }
544 
545 - (BOOL)allowsMultipleSelection
546 {
547  return _allowsMultipleSelection;
548 }
549 
550 - (void)setAllowsMultipleSelection:(BOOL)shouldAllow
551 {
552  if (_allowsMultipleSelection === shouldAllow)
553  return;
554 
555  _allowsMultipleSelection = shouldAllow;
556  [_tableViews makeObjectsPerformSelector:@selector(setAllowsMultipleSelection:) withObject:shouldAllow];
557 }
558 
559 - (BOOL)allowsEmptySelection
560 {
561  return _allowsEmptySelection;
562 }
563 
564 - (void)setAllowsEmptySelection:(BOOL)shouldAllow
565 {
566  if (_allowsEmptySelection === shouldAllow)
567  return;
568 
569  _allowsEmptySelection = shouldAllow;
570  [_tableViews makeObjectsPerformSelector:@selector(setAllowsEmptySelection:) withObject:shouldAllow];
571 }
572 
573 - (CPIndexSet)selectedRowIndexesInColumn:(unsigned)column
574 {
575  if (column < 0 || column > [self lastColumn] +1)
576  return [CPIndexSet indexSet];
577 
578  return [[self tableViewInColumn:column] selectedRowIndexes];
579 }
580 
581 - (void)selectRowIndexes:(CPIndexSet)indexSet inColumn:(unsigned)column
582 {
583  if (column < 0 || column > [self lastColumn] + 1)
584  return;
585 
586  if ([_delegate respondsToSelector:@selector(browser:selectionIndexesForProposedSelection:inColumn:)])
587  indexSet = [_delegate browser:self selectionIndexesForProposedSelection:indexSet inColumn:column];
588 
589  if ([_delegate respondsToSelector:@selector(browser:shouldSelectRowIndexes:inColumn:)] &&
590  ![_delegate browser:self shouldSelectRowIndexes:indexSet inColumn:column])
591  return;
592 
593  if ([_delegate respondsToSelector:@selector(browserSelectionIsChanging:)])
594  [_delegate browserSelectionIsChanging:self];
595 
596  if (column > [self lastColumn])
597  [self addColumn];
598 
599  [self setLastColumn:column];
600 
601  [[self tableViewInColumn:column] selectRowIndexes:indexSet byExtendingSelection:NO];
602 
603  [self scrollColumnToVisible:column];
604 
605  if ([_delegate respondsToSelector:@selector(browserSelectionDidChange:)])
606  [_delegate browserSelectionDidChange:self];
607 }
608 
609 - (void)setBackgroundColor:(CPColor)aColor
610 {
611  [super setBackgroundColor:aColor];
612  [_contentView setBackgroundColor:aColor];
613 }
614 
615 - (BOOL)acceptsFirstResponder
616 {
617  return YES;
618 }
619 
620 // DRAG AND DROP
621 
622 - (void)registerForDraggedTypes:(CPArray)types
623 {
624  [super registerForDraggedTypes:types];
625  [_tableViews makeObjectsPerformSelector:@selector(registerForDraggedTypes:) withObject:types];
626 }
627 
628 - (BOOL)canDragRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(int)columnIndex withEvent:(CPEvent)dragEvent
629 {
630  if ([_delegate respondsToSelector:@selector(browser:canDragRowsWithIndexes:inColumn:withEvent:)])
631  return [_delegate browser:self canDragRowsWithIndexes:rowIndexes inColumn:columnIndex withEvent:dragEvent];
632 
633  return YES;
634 }
635 
636 - (CPImage)draggingImageForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(int)columnIndex withEvent:(CPEvent)dragEvent offset:(CGPoint)dragImageOffset
637 {
638  if ([_delegate respondsToSelector:@selector(browser:draggingImageForRowsWithIndexes:inColumn:withEvent:offset:)])
639  return [_delegate browser:self draggingImageForRowsWithIndexes:rowIndexes inColumn:columnIndex withEvent:dragEvent offset:dragImageOffset];
640 
641  return nil;
642 }
643 
644 - (CPView)draggingViewForRowsWithIndexes:(CPIndexSet)rowIndexes inColumn:(int)columnIndex withEvent:(CPEvent)dragEvent offset:(CGPoint)dragImageOffset
645 {
646  if ([_delegate respondsToSelector:@selector(browser:draggingViewForRowsWithIndexes:inColumn:withEvent:offset:)])
647  return [_delegate browser:self draggingViewForRowsWithIndexes:rowIndexes inColumn:columnIndex withEvent:dragEvent offset:dragImageOffset];
648 
649  return nil;
650 }
651 
652 @end
653 
654 @implementation CPBrowser (CPCoding)
655 
656 - (id)initWithCoder:(CPCoder)aCoder
657 {
658  self = [super initWithCoder:aCoder];
659 
660  if (self)
661  {
662  [self _init];
663 
664  _allowsEmptySelection = [aCoder decodeBoolForKey:@"CPBrowserAllowsEmptySelectionKey"];
665  _allowsMultipleSelection = [aCoder decodeBoolForKey:@"CPBrowserAllowsMultipleSelectionKey"];
666  _prototypeView = [aCoder decodeObjectForKey:@"CPBrowserPrototypeViewKey"];
667  _rowHeight = [aCoder decodeFloatForKey:@"CPBrowserRowHeightKey"];
668  _imageWidth = [aCoder decodeFloatForKey:@"CPBrowserImageWidthKey"];
669  _minColumnWidth = [aCoder decodeFloatForKey:@"CPBrowserMinColumnWidthKey"];
670  _columnWidths = [aCoder decodeObjectForKey:@"CPBrowserColumnWidthsKey"];
671 
672  [self setDelegate:[aCoder decodeObjectForKey:@"CPBrowserDelegateKey"]];
673  [self setAutohidesScroller:[aCoder decodeBoolForKey:@"CPBrowserAutohidesScrollerKey"]];
674  }
675 
676  return self;
677 }
678 
679 - (void)encodeWithCoder:(CPCoder)aCoder
680 {
681  // Don't encode the subviews, they're transient and will be recreated from data.
682  var actualSubviews = _subviews;
683  _subviews = [];
684  [super encodeWithCoder:aCoder];
685  _subviews = actualSubviews;
686 
687  [aCoder encodeBool:[self autohidesScroller] forKey:@"CPBrowserAutohidesScrollerKey"];
688  [aCoder encodeBool:_allowsEmptySelection forKey:@"CPBrowserAllowsEmptySelectionKey"];
689  [aCoder encodeBool:_allowsMultipleSelection forKey:@"CPBrowserAllowsMultipleSelectionKey"];
690  [aCoder encodeObject:_delegate forKey:@"CPBrowserDelegateKey"];
691  [aCoder encodeObject:_prototypeView forKey:@"CPBrowserPrototypeViewKey"];
692  [aCoder encodeFloat:_rowHeight forKey:@"CPBrowserRowHeightKey"];
693  [aCoder encodeFloat:_imageWidth forKey:@"CPBrowserImageWidthKey"];
694  [aCoder encodeFloat:_minColumnWidth forKey:@"CPBrowserMinColumnWidthKey"];
695  [aCoder encodeObject:_columnWidths forKey:@"CPBrowserColumnWidthsKey"];
696 }
697 
698 @end
699 
700 
701 var _CPBrowserResizeControlBackgroundImage = nil;
702 
703 @implementation _CPBrowserResizeControl : CPView
704 {
705  CGPoint _mouseDownX;
706  CPBrowser _browser;
707  unsigned _index;
708  unsigned _width;
709 }
710 
711 + (CPImage)backgroundImage
712 {
713  if (!_CPBrowserResizeControlBackgroundImage)
714  {
715  var path = [[CPBundle bundleForClass:[self class]] pathForResource:"browser-resize-control.png"];
716  _CPBrowserResizeControlBackgroundImage = [[CPImage alloc] initWithContentsOfFile:path
717  size:CGSizeMake(15, 14)];
718  }
719 
720  return _CPBrowserResizeControlBackgroundImage;
721 }
722 
723 - (id)initWithFrame:(CGRect)aFrame
724 {
725  if (self = [super initWithFrame:aFrame])
726  [self setBackgroundColor:[CPColor colorWithPatternImage:[[self class] backgroundImage]]];
727 
728  return self;
729 }
730 
731 - (void)mouseDown:(CPEvent)anEvent
732 {
733  _mouseDownX = [anEvent locationInWindow].x;
734  _browser = [[self superview] _browser];
735  _index = [_browser columnOfTableView:[[self superview] documentView]];
736  _width = [_browser widthOfColumn:_index];
737 }
738 
739 - (void)mouseDragged:(CPEvent)anEvent
740 {
741  var deltaX = [anEvent locationInWindow].x - _mouseDownX;
742  [_browser setWidth:_width + deltaX ofColumn:_index];
743 }
744 
745 - (void)mouseUp:(CPEvent)anEvent
746 {
747 }
748 
749 @end
750 
751 @implementation _CPBrowserScrollView : CPScrollView
752 {
753  _CPBrowserResizeControl _resizeControl;
754  CPBrowser _browser;
755 }
756 
757 - (void)initWithFrame:(CGRect)aFrame
758 {
759  if (self = [super initWithFrame:aFrame])
760  {
761  _resizeControl = [[_CPBrowserResizeControl alloc] initWithFrame:CGRectMakeZero()];
762  [self addSubview:_resizeControl];
763  }
764 
765  return self;
766 }
767 
768 - (void)reflectScrolledClipView:(CPClipView)aClipView
769 {
770  [super reflectScrolledClipView:aClipView];
771 
772  var frame = [_verticalScroller frame];
773  frame.size.height = CGRectGetHeight([self bounds]) - 14.0 - frame.origin.y;
774  [_verticalScroller setFrameSize:frame.size];
775 
776  var resizeFrame = CGRectMake(CGRectGetMinX(frame), CGRectGetMaxY(frame), [CPScroller scrollerWidth], 14.0);
777  [_resizeControl setFrame:resizeFrame];
778 }
779 
780 @end
781 
782 @implementation _CPBrowserTableView : CPTableView
783 {
784  CPBrowser _browser;
785 }
786 
787 - (id)initWithFrame:(CGRect)aFrame browser:(CPBrowser)aBrowser
788 {
789  if (self = [super initWithFrame:aFrame])
790  _browser = aBrowser;
791 
792  return self;
793 }
794 
795 - (BOOL)acceptsFirstResponder
796 {
797  return NO;
798 }
799 
800 - (void)mouseDown:(CPEvent)anEvent
801 {
802  [super mouseDown:anEvent];
803  [[self window] makeFirstResponder:_browser];
804 }
805 
806 - (CPView)browserView
807 {
808  return _browser;
809 }
810 
811 - (BOOL)canDragRowsWithIndexes:(CPIndexSet)rowIndexes atPoint:(CGPoint)mouseDownPoint
812 {
813  return [_browser canDragRowsWithIndexes:rowIndexes inColumn:[_browser columnOfTableView:self] withEvent:[CPApp currentEvent]];
814 }
815 
816 - (CPImage)dragImageForRowsWithIndexes:(CPIndexSet)dragRows tableColumns:(CPArray)theTableColumns event:(CPEvent)dragEvent offset:(CPPointPointer)dragImageOffset
817 {
818  return [_browser draggingImageForRowsWithIndexes:dragRows inColumn:[_browser columnOfTableView:self] withEvent:dragEvent offset:dragImageOffset] ||
819  [super dragImageForRowsWithIndexes:dragRows tableColumns:theTableColumns event:dragEvent offset:dragImageOffset];
820 }
821 
822 - (CPView)dragViewForRowsWithIndexes:(CPIndexSet)dragRows tableColumns:(CPArray)theTableColumns event:(CPEvent)dragEvent offset:(CPPoint)dragViewOffset
823 {
824  var count = theTableColumns.length;
825  while (count--)
826  {
827  if ([theTableColumns[count] identifier] === "Leaf")
828  [theTableColumns removeObject:theTableColumns[count]];
829  }
830 
831  return [_browser draggingViewForRowsWithIndexes:dragRows inColumn:[_browser columnOfTableView:self] withEvent:dragEvent offset:dragViewOffset] ||
832  [super dragViewForRowsWithIndexes:dragRows tableColumns:theTableColumns event:dragEvent offset:dragViewOffset];
833 }
834 
835 - (void)moveUp:(id)sender
836 {
837  [super moveUp:sender];
838  [_browser selectRow:[self selectedRow] inColumn:[_browser selectedColumn]];
839 }
840 
841 - (void)moveDown:(id)sender
842 {
843  [super moveDown:sender];
844  [_browser selectRow:[self selectedRow] inColumn:[_browser selectedColumn]];
845 }
846 
847 - (void)moveLeft:(id)sender
848 {
849  var previousColumn = [_browser selectedColumn] - 1,
850  selectedRow = [_browser selectedRowInColumn:previousColumn];
851 
852  [_browser selectRow:selectedRow inColumn:previousColumn];
853 }
854 
855 - (void)moveRight:(id)sender
856 {
857  [_browser selectRow:0 inColumn:[_browser selectedColumn] + 1];
858 }
859 
860 @end
861 
862 
863 @implementation _CPBrowserTableDelegate : CPObject
864 {
865  CPBrowser _browser;
866  unsigned _index;
867  id _delegate;
868  id _item;
869 }
870 
871 - (unsigned)numberOfRowsInTableView:(CPTableView)aTableView
872 {
873  return [_delegate browser:_browser numberOfChildrenOfItem:_item];
874 }
875 
876 - (void)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)column row:(unsigned)row
877 {
878  if ([column identifier] === "Image")
879  return [_delegate browser:_browser imageValueForItem:[self childAtIndex:row]];
880  else if ([column identifier] === "Leaf")
881  return ![_browser isLeafItem:[self childAtIndex:row]];
882  else
883  return [_delegate browser:_browser objectValueForItem:[self childAtIndex:row]];
884 }
885 
886 - (void)_tableViewDoubleClicked:(CPTableView)aTableView
887 {
888  [_browser doDoubleClick:self];
889 }
890 
891 - (void)_tableViewClicked:(CPTableView)aTableView
892 {
893  var selectedIndexes = [aTableView selectedRowIndexes];
894  [_browser _column:_index clickedRow:[selectedIndexes count] === 1 ? [selectedIndexes firstIndex] : -1];
895 }
896 
897 - (void)tableViewSelectionDidChange:(CPNotification)aNotification
898 {
899  var selectedIndexes = [[aNotification object] selectedRowIndexes];
900  [_browser selectRowIndexes:selectedIndexes inColumn:_index];
901 }
902 
903 - (id)childAtIndex:(unsigned)index
904 {
905  return [_delegate browser:_browser child:index ofItem:_item];
906 }
907 
908 - (BOOL)tableView:(CPTableView)aTableView acceptDrop:(id)info row:(int)row dropOperation:(CPTableViewDropOperation)operation
909 {
910  if ([_delegate respondsToSelector:@selector(browser:acceptDrop:atRow:column:dropOperation:)])
911  return [_delegate browser:_browser acceptDrop:info atRow:row column:_index dropOperation:operation];
912  else
913  return NO;
914 }
915 
916 - (CPDragOperation)tableView:(CPTableView)aTableView validateDrop:(id)info proposedRow:(int)row proposedDropOperation:(CPTableViewDropOperation)operation
917 {
918  if ([_delegate respondsToSelector:@selector(browser:validateDrop:proposedRow:column:dropOperation:)])
919  return [_delegate browser:_browser validateDrop:info proposedRow:row column:_index dropOperation:operation];
920  else
921  return CPDragOperationNone;
922 }
923 
924 - (BOOL)tableView:(CPTableView)aTableView writeRowsWithIndexes:(CPIndexSet)rowIndexes toPasteboard:(CPPasteboard)pboard
925 {
926  if ([_delegate respondsToSelector:@selector(browser:writeRowsWithIndexes:inColumn:toPasteboard:)])
927  return [_delegate browser:_browser writeRowsWithIndexes:rowIndexes inColumn:_index toPasteboard:pboard];
928  else
929  return NO;
930 }
931 
932 - (BOOL)respondsToSelector:(SEL)aSelector
933 {
934  if (aSelector === @selector(browser:writeRowsWithIndexes:inColumn:toPasteboard:))
935  return [_delegate respondsToSelector:@selector(browser:writeRowsWithIndexes:inColumn:toPasteboard:)];
936  else
937  return [super respondsToSelector:aSelector];
938 }
939 
940 @end
941 
942 @implementation _CPBrowserLeafView : CPView
943 {
944  BOOL _isLeaf;
945  CPImage _branchImage;
946  CPImage _highlightedBranchImage;
947 }
948 
949 - (BOOL)objectValue
950 {
951  return _isLeaf;
952 }
953 
954 - (void)setObjectValue:(id)aValue
955 {
956  _isLeaf = !!aValue;
957  [self setNeedsLayout];
958 }
959 
960 - (CGRect)rectForEphemeralSubviewNamed:(CPString)aName
961 {
962  if (aName === "image-view")
963  return CGRectInset([self bounds], 1, 1);
964 
965  return [super rectForEphemeralSubviewNamed:aName];
966 }
967 
968 - (CPView)createEphemeralSubviewNamed:(CPString)aName
969 {
970  if (aName === "image-view")
971  return [[CPImageView alloc] initWithFrame:CGRectMakeZero()];
972 
973  return [super createEphemeralSubviewNamed:aName];
974 }
975 
976 - (void)layoutSubviews
977 {
978  var imageView = [self layoutEphemeralSubviewNamed:@"image-view"
979  positioned:CPWindowAbove
980  relativeToEphemeralSubviewNamed:nil],
981  isHighlighted = [self themeState] & CPThemeStateSelectedDataView;
982 
983  [imageView setImage: _isLeaf ? (isHighlighted ? _highlightedBranchImage : _branchImage) : nil];
984  [imageView setImageScaling:CPImageScaleNone];
985 }
986 
987 - (void)encodeWithCoder:(CPCoder)aCoder
988 {
989  [super encodeWithCoder:aCoder];
990 
991  [aCoder encodeBool:_isLeaf forKey:"_CPBrowserLeafViewIsLeafKey"];
992  [aCoder encodeObject:_branchImage forKey:"_CPBrowserLeafViewBranchImageKey"];
993  [aCoder encodeObject:_highlightedBranchImage forKey:"_CPBrowserLeafViewHighlightedBranchImageKey"];
994 }
995 
996 - (void)initWithCoder:(CPCoder)aCoder
997 {
998  if (self = [super initWithCoder:aCoder])
999  {
1000  _isLeaf = [aCoder decodeBoolForKey:"_CPBrowserLeafViewIsLeafKey"];
1001  _branchImage = [aCoder decodeObjectForKey:"_CPBrowserLeafViewBranchImageKey"];
1002  _highlightedBranchImage = [aCoder decodeObjectForKey:"_CPBrowserLeafViewHighlightedBranchImageKey"];
1003  }
1004 
1005  return self;
1006 }
1007 
1008 @end
1009 
1011 
1015 - (SEL)doubleAction
1016 {
1017  return _doubleAction;
1018 }
1019 
1023 - (void)setDoubleAction:(SEL)aValue
1024 {
1025  _doubleAction = aValue;
1026 }
1027 
1031 - (Class)tableViewClass
1032 {
1033  return _tableViewClass;
1034 }
1035 
1039 - (void)setTableViewClass:(Class)aValue
1040 {
1041  _tableViewClass = aValue;
1042 }
1043 
1047 - (float)defaultColumnWidth
1048 {
1049  return _defaultColumnWidth;
1050 }
1051 
1055 - (void)setDefaultColumnWidth:(float)aValue
1056 {
1057  _defaultColumnWidth = aValue;
1058 }
1059 
1060 @end