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