00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023 @import <Foundation/CPArray.j>
00024 @import <Foundation/CPData.j>
00025 @import <Foundation/CPIndexSet.j>
00026 @import <Foundation/CPKeyedArchiver.j>
00027 @import <Foundation/CPKeyedUnarchiver.j>
00028
00029 @import <AppKit/CPView.j>
00030
00031
00064 @implementation CPCollectionView : CPView
00065 {
00066 CPArray _content;
00067 CPArray _items;
00068
00069 CPData _itemData;
00070 CPCollectionViewItem _itemPrototype;
00071 CPCollectionViewItem _itemForDragging;
00072 CPMutableArray _cachedItems;
00073
00074 unsigned _maxNumberOfRows;
00075 unsigned _maxNumberOfColumns;
00076
00077 CGSize _minItemSize;
00078 CGSize _maxItemSize;
00079
00080 float _tileWidth;
00081
00082 BOOL _isSelectable;
00083 BOOL _allowsMultipleSelection;
00084 BOOL _allowsEmptySelection;
00085 CPIndexSet _selectionIndexes;
00086
00087 CGSize _itemSize;
00088
00089 float _horizontalMargin;
00090 float _verticalMargin;
00091
00092 unsigned _numberOfRows;
00093 unsigned _numberOfColumns;
00094
00095 id _delegate;
00096 }
00097
00098 - (id)initWithFrame:(CGRect)aFrame
00099 {
00100 self = [super initWithFrame:aFrame];
00101
00102 if (self)
00103 {
00104 _items = [];
00105 _content = [];
00106
00107 _cachedItems = [];
00108
00109 _itemSize = CGSizeMakeZero();
00110 _minItemSize = CGSizeMakeZero();
00111 _maxItemSize = CGSizeMakeZero();
00112
00113 _verticalMargin = 5.0;
00114 _tileWidth = -1.0;
00115
00116 _selectionIndexes = [CPIndexSet indexSet];
00117 _allowsEmptySelection = YES;
00118 _isSelectable = YES;
00119 }
00120
00121 return self;
00122 }
00123
00128 - (void)setItemPrototype:(CPCollectionViewItem)anItem
00129 {
00130 _itemData = [CPKeyedArchiver archivedDataWithRootObject:anItem];
00131 _itemForDragging = anItem
00132 _itemPrototype = anItem;
00133
00134 [self reloadContent];
00135 }
00136
00140 - (CPCollectionViewItem)itemPrototype
00141 {
00142 return _itemPrototype;
00143 }
00144
00149 - (CPCollectionViewItem)newItemForRepresentedObject:(id)anObject
00150 {
00151 var item = nil;
00152
00153 if (_cachedItems.length)
00154 item = _cachedItems.pop();
00155 else
00156 item = [CPKeyedUnarchiver unarchiveObjectWithData:_itemData];
00157
00158 [item setRepresentedObject:anObject];
00159 [[item view] setFrameSize:_itemSize];
00160
00161 return item;
00162 }
00163
00164
00168 - (BOOL)acceptsFirstResponder
00169 {
00170 return YES;
00171 }
00172
00176 - (BOOL)isFirstResponder
00177 {
00178 return [[self window] firstResponder] == self;
00179 }
00180
00181
00188 - (void)setContent:(CPArray)anArray
00189 {
00190 if (_content == anArray)
00191 return;
00192
00193 _content = anArray;
00194
00195 [self reloadContent];
00196 }
00197
00201 - (CPArray)content
00202 {
00203 return _content;
00204 }
00205
00209 - (CPArray)items
00210 {
00211 return _items;
00212 }
00213
00214
00219 - (void)setSelectable:(BOOL)isSelectable
00220 {
00221 if (_isSelectable == isSelectable)
00222 return;
00223
00224 _isSelectable = isSelectable;
00225
00226 if (!_isSelectable)
00227 {
00228 var index = CPNotFound;
00229
00230 while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound)
00231 [_items[index] setSelected:NO];
00232 }
00233 }
00234
00239 - (BOOL)isSelected
00240 {
00241 return _isSelected;
00242 }
00243
00248 - (void)setAllowsEmptySelection:(BOOL)shouldAllowEmptySelection
00249 {
00250 _allowsEmptySelection = shouldAllowEmptySelection;
00251 }
00252
00256 - (BOOL)allowsEmptySelection
00257 {
00258 return _allowsEmptySelection;
00259 }
00260
00265 - (void)setAllowsMultipleSelection:(BOOL)shouldAllowMultipleSelection
00266 {
00267 _allowsMultipleSelection = shouldAllowMultipleSelection;
00268 }
00269
00273 - (BOOL)allowsMultipleSelection
00274 {
00275 return _allowsMultipleSelection;
00276 }
00277
00282 - (void)setSelectionIndexes:(CPIndexSet)anIndexSet
00283 {
00284 if (_selectionIndexes == anIndexSet || !_isSelectable)
00285 return;
00286
00287 var index = CPNotFound;
00288
00289 while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound)
00290 [_items[index] setSelected:NO];
00291
00292 _selectionIndexes = anIndexSet;
00293
00294 var index = CPNotFound;
00295
00296 while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound)
00297 [_items[index] setSelected:YES];
00298
00299 if ([_delegate respondsToSelector:@selector(collectionViewDidChangeSelection:)])
00300 [_delegate collectionViewDidChangeSelection:self]
00301 }
00302
00306 - (CPIndexSet)selectionIndexes
00307 {
00308 return _selectionIndexes;
00309 }
00310
00311
00312 - (void)reloadContent
00313 {
00314
00315 var count = _items.length;
00316
00317 while (count--)
00318 {
00319 [[_items[count] view] removeFromSuperview];
00320 _cachedItems.push(_items[count]);
00321 }
00322
00323 _items = [];
00324
00325 if (!_itemData || !_content)
00326 return;
00327
00328 var index = 0;
00329
00330 count = _content.length;
00331
00332 for (; index < count; ++index)
00333 {
00334 _items.push([self newItemForRepresentedObject:_content[index]]);
00335
00336 [self addSubview:[_items[index] view]];
00337 }
00338
00339 [self tile];
00340 }
00341
00342
00343 - (void)tile
00344 {
00345 var width = CGRectGetWidth([self bounds]);
00346
00347 if (![_content count] || width == _tileWidth)
00348 return;
00349
00350
00351
00352
00353 var itemSize = CGSizeMakeCopy(_minItemSize);
00354
00355 _numberOfColumns = MAX(1.0, FLOOR(width / itemSize.width));
00356
00357 if (_maxNumberOfColumns > 0)
00358 _numberOfColumns = MIN(_maxNumberOfColumns, _numberOfColumns);
00359
00360 var remaining = width - _numberOfColumns * itemSize.width,
00361 itemsNeedSizeUpdate = NO;
00362
00363 if (remaining > 0 && itemSize.width < _maxItemSize.width)
00364 itemSize.width = MIN(_maxItemSize.width, itemSize.width + FLOOR(remaining / _numberOfColumns));
00365
00366
00367 if (_maxNumberOfColumns == 1 && itemSize.width < _maxItemSize.width && itemSize.width < width)
00368 itemSize.width = MIN(_maxItemSize.width, width);
00369
00370 if (!CGSizeEqualToSize(_itemSize, itemSize))
00371 {
00372 _itemSize = itemSize;
00373 itemsNeedSizeUpdate = YES;
00374 }
00375
00376 var index = 0,
00377 count = _items.length;
00378
00379 if (_maxNumberOfColumns > 0 && _maxNumberOfRows > 0)
00380 count = MIN(count, _maxNumberOfColumns * _maxNumberOfRows);
00381
00382 _numberOfRows = CEIL(count / _numberOfColumns);
00383
00384 _horizontalMargin = FLOOR((width - _numberOfColumns * itemSize.width) / (_numberOfColumns + 1));
00385
00386 var x = _horizontalMargin,
00387 y = -itemSize.height;
00388
00389 for (; index < count; ++index)
00390 {
00391 if (index % _numberOfColumns == 0)
00392 {
00393 x = _horizontalMargin;
00394 y += _verticalMargin + itemSize.height;
00395 }
00396
00397 var view = [_items[index] view];
00398
00399 [view setFrameOrigin:CGPointMake(x, y)];
00400
00401 if (itemsNeedSizeUpdate)
00402 [view setFrameSize:_itemSize];
00403
00404 x += itemSize.width + _horizontalMargin;
00405 }
00406
00407 _tileWidth = width;
00408 [self setFrameSize:CGSizeMake(width, y + itemSize.height + _verticalMargin)];
00409 _tileWidth = -1.0;
00410 }
00411
00412 - (void)resizeSubviewsWithOldSize:(CGSize)aSize
00413 {
00414 [self tile];
00415 }
00416
00417
00422 - (void)setMaxNumberOfRows:(unsigned)aMaxNumberOfRows
00423 {
00424 if (_maxNumberOfRows == aMaxNumberOfRows)
00425 return;
00426
00427 _maxNumberOfRows = aMaxNumberOfRows;
00428
00429 [self tile];
00430 }
00431
00435 - (unsigned)maxNumberOfRows
00436 {
00437 return _maxNumberOfRows;
00438 }
00439
00444 - (void)setMaxNumberOfColumns:(unsigned)aMaxNumberOfColumns
00445 {
00446 if (_maxNumberOfColumns == aMaxNumberOfColumns)
00447 return;
00448
00449 _maxNumberOfColumns = aMaxNumberOfColumns;
00450
00451 [self tile];
00452 }
00453
00457 - (unsigned)maxNumberOfColumns
00458 {
00459 return _maxNumberOfColumns;
00460 }
00461
00465 - (unsigned)numberOfRows
00466 {
00467 return _numberOfRows;
00468 }
00469
00474 - (unsigned)numberOfColumns
00475 {
00476 return _numberOfColumns;
00477 }
00478
00483 - (void)setMinItemSize:(CGSize)aSize
00484 {
00485 if (CGSizeEqualToSize(_minItemSize, aSize))
00486 return;
00487
00488 _minItemSize = CGSizeMakeCopy(aSize);
00489
00490 [self tile];
00491 }
00492
00496 - (CGSize)minItemSize
00497 {
00498 return _minItemSize;
00499 }
00500
00505 - (void)setMaxItemSize:(CGSize)aSize
00506 {
00507 if (CGSizeEqualToSize(_maxItemSize, aSize))
00508 return;
00509
00510 _maxItemSize = CGSizeMakeCopy(aSize);
00511
00512 [self tile];
00513 }
00514
00518 - (CGSize)maxItemSize
00519 {
00520 return _maxItemSize;
00521 }
00522
00523 - (void)mouseUp:(CPEvent)anEvent
00524 {
00525 if ([_selectionIndexes count] && [anEvent clickCount] == 2 && [_delegate respondsToSelector:@selector(collectionView:didDoubleClickOnItemAtIndex:)])
00526 [_delegate collectionView:self didDoubleClickOnItemAtIndex:[_selectionIndexes firstIndex]];
00527 }
00528
00529 - (void)mouseDown:(CPEvent)anEvent
00530 {
00531 var location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
00532 row = FLOOR(location.y / (_itemSize.height + _verticalMargin)),
00533 column = FLOOR(location.x / (_itemSize.width + _horizontalMargin)),
00534 index = row * _numberOfColumns + column;
00535
00536 if (index >= 0 && index < _items.length)
00537 [self setSelectionIndexes:[CPIndexSet indexSetWithIndex:index]];
00538 else if (_allowsEmptySelection)
00539 [self setSelectionIndexes:[CPIndexSet indexSet]];
00540 }
00541
00542 - (void)mouseDragged:(CPEvent)anEvent
00543 {
00544 if (![_delegate respondsToSelector:@selector(collectionView:dragTypesForItemsAtIndexes:)])
00545 return;
00546
00547
00548 if (![_selectionIndexes count])
00549 return;
00550
00551
00552 var dragTypes = [_delegate collectionView:self dragTypesForItemsAtIndexes:_selectionIndexes];
00553
00554 [[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:dragTypes owner:self];
00555
00556 var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
00557
00558 [_itemForDragging setRepresentedObject:_content[[_selectionIndexes firstIndex]]];
00559
00560 var view = [_itemForDragging view],
00561 frame = [view frame];
00562
00563 [view setFrameSize:_itemSize];
00564 [view setAlphaValue:0.7];
00565
00566 [self dragView:view
00567 at:[[_items[[_selectionIndexes firstIndex]] view] frame].origin
00568 offset:CGPointMakeZero()
00569 event:anEvent
00570 pasteboard:nil
00571 source:self
00572 slideBack:YES];
00573 }
00574
00580 - (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
00581 {
00582 [aPasteboard setData:[_delegate collectionView:self dataForItemsAtIndexes:_selectionIndexes forType:aType] forType:aType];
00583 }
00584
00585
00586
00592 - (void)setVerticalMargin:(float)aVerticalMargin
00593 {
00594 if (_verticalMargin == aVerticalMargin)
00595 return;
00596
00597 _verticalMargin = aVerticalMargin;
00598
00599 [self tile];
00600 }
00601
00606 - (float)verticalMargin
00607 {
00608 return _verticalMargin;
00609 }
00610
00615 - (void)setDelegate:(id)aDelegate
00616 {
00617 _delegate = aDelegate;
00618 }
00619
00623 - (id)delegate
00624 {
00625 return _delegate;
00626 }
00627
00628 @end
00629
00633 @implementation CPCollectionViewItem : CPObject
00634 {
00635 id _representedObject;
00636
00637 CPView _view;
00638
00639 BOOL _isSelected;
00640 }
00641
00642
00647 - (void)setRepresentedObject:(id)anObject
00648 {
00649 if (_representedObject == anObject)
00650 return;
00651
00652 _representedObject = anObject;
00653
00654
00655 [_view setRepresentedObject:anObject];
00656 }
00657
00661 - (id)representedObject
00662 {
00663 return _representedObject;
00664 }
00665
00666
00671 - (void)setView:(CPView)aView
00672 {
00673 _view = aView;
00674 }
00675
00679 - (CPView)view
00680 {
00681 return _view;
00682 }
00683
00684
00689 - (void)setSelected:(BOOL)shouldBeSelected
00690 {
00691 if (_isSelected == shouldBeSelected)
00692 return;
00693
00694 _isSelected = shouldBeSelected;
00695
00696
00697 [_view setSelected:_isSelected];
00698 }
00699
00703 - (BOOL)isSelected
00704 {
00705 return _isSelected;
00706 }
00707
00708
00712 - (CPCollectionView)collectionView
00713 {
00714 return [_view superview];
00715 }
00716
00717 @end
00718
00719 var CPCollectionViewMinItemSizeKey = @"CPCollectionViewMinItemSizeKey",
00720 CPCollectionViewMaxItemSizeKey = @"CPCollectionViewMaxItemSizeKey",
00721 CPCollectionViewVerticalMarginKey = @"CPCollectionViewVerticalMarginKey";
00722
00723
00724 @implementation CPCollectionView (CPCoding)
00725
00726 - (id)initWithCoder:(CPCoder)aCoder
00727 {
00728 self = [super initWithCoder:aCoder];
00729
00730 if (self)
00731 {
00732 _items = [];
00733 _content = [];
00734
00735 _cachedItems = [];
00736
00737 _itemSize = CGSizeMakeZero();
00738 _minItemSize = [aCoder decodeSizeForKey:CPCollectionViewMinItemSizeKey];
00739 _maxItemSize = [aCoder decodeSizeForKey:CPCollectionViewMaxItemSizeKey];
00740
00741 _verticalMargin = [aCoder decodeSizeForKey:CPCollectionViewVerticalMarginKey];
00742 _tileWidth = -1.0;
00743
00744 _selectionIndexes = [CPIndexSet indexSet];
00745 }
00746
00747 return self;
00748 }
00749
00750 - (void)encodeWithCoder:(CPCoder)aCoder
00751 {
00752 [super encodeWithCoder:aCoder];
00753
00754 [aCoder encodeSize:_minItemSize forKey:CPCollectionViewMinItemSizeKey];
00755 [aCoder encodeSize:_maxItemSize forKey:CPCollectionViewMaxItemSizeKey];
00756
00757 [aCoder encodeSize:_verticalMargin forKey:CPCollectionViewVerticalMarginKey];
00758 }
00759
00760 @end
00761
00762 var CPCollectionViewItemViewKey = @"CPCollectionViewItemViewKey";
00763
00764 @implementation CPCollectionViewItem (CPCoding)
00765
00766
00767
00768
00769 - (id)copy
00770 {
00771
00772 }
00773
00774 @end
00775
00776 var CPCollectionViewItemViewKey = @"CPCollectionViewItemViewKey";
00777
00778 @implementation CPCollectionViewItem (CPCoding)
00779
00785 - (id)initWithCoder:(CPCoder)aCoder
00786 {
00787 self = [super init];
00788
00789 if (self)
00790 _view = [aCoder decodeObjectForKey:CPCollectionViewItemViewKey];
00791
00792 return self;
00793 }
00794
00799 - (void)encodeWithCoder:(CPCoder)aCoder
00800 {
00801 [aCoder encodeObject:_view forKey:CPCollectionViewItemViewKey];
00802 }
00803
00804 @end