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
00062 @implementation CPCollectionView : CPView
00063 {
00064 CPArray _content;
00065 CPArray _items;
00066
00067 CPData _itemData;
00068 CPCollectionViewItem _itemPrototype;
00069 CPCollectionViewItem _itemForDragging;
00070 CPMutableArray _cachedItems;
00071
00072 unsigned _maxNumberOfRows;
00073 unsigned _maxNumberOfColumns;
00074
00075 CGSize _minItemSize;
00076 CGSize _maxItemSize;
00077
00078 float _tileWidth;
00079
00080 BOOL _isSelectable;
00081 BOOL _allowsMultipleSelection;
00082 CPIndexSet _selectionIndexes;
00083
00084 CGSize _itemSize;
00085
00086 float _horizontalMargin;
00087 float _verticalMargin;
00088
00089 unsigned _numberOfRows;
00090 unsigned _numberOfColumns;
00091
00092 id _delegate;
00093 }
00094
00095 - (id)initWithFrame:(CGRect)aFrame
00096 {
00097 self = [super initWithFrame:aFrame];
00098
00099 if (self)
00100 {
00101 _items = [];
00102 _content = [];
00103
00104 _cachedItems = [];
00105
00106 _itemSize = CGSizeMakeZero();
00107 _minItemSize = CGSizeMakeZero();
00108 _maxItemSize = CGSizeMakeZero();
00109
00110 _verticalMargin = 5.0;
00111 _tileWidth = -1.0;
00112
00113 _selectionIndexes = [CPIndexSet indexSet];
00114 }
00115
00116 return self;
00117 }
00118
00123 - (void)setItemPrototype:(CPCollectionViewItem)anItem
00124 {
00125 _itemData = [CPKeyedArchiver archivedDataWithRootObject:anItem];
00126 _itemForDragging = anItem
00127
00128 [self reloadContent];
00129 }
00130
00134 - (CPCollectionViewItem)itemPrototype
00135 {
00136 return _itemPrototype;
00137 }
00138
00143 - (CPCollectionViewItem)newItemForRepresentedObject:(id)anObject
00144 {
00145 var item = nil;
00146
00147 if (_cachedItems.length)
00148 item = _cachedItems.pop();
00149 else
00150 item = [CPKeyedUnarchiver unarchiveObjectWithData:_itemData];
00151
00152 [item setRepresentedObject:anObject];
00153 [[item view] setFrameSize:_itemSize];
00154
00155 return item;
00156 }
00157
00158
00162 - (BOOL)acceptsFirstResponder
00163 {
00164 return YES;
00165 }
00166
00170 - (BOOL)isFirstResponder
00171 {
00172 return [[self window] firstResponder] == self;
00173 }
00174
00175
00182 - (void)setContent:(CPArray)anArray
00183 {
00184 if (_content == anArray)
00185 return;
00186
00187 _content = anArray;
00188
00189 [self reloadContent];
00190 }
00191
00195 - (CPArray)content
00196 {
00197 return _content;
00198 }
00199
00203 - (CPArray)items
00204 {
00205 return _items;
00206 }
00207
00208
00213 - (void)setSelectable:(BOOL)isSelectable
00214 {
00215 if (_isSelectable == isSelectable)
00216 return;
00217
00218 _isSelectable = isSelectable;
00219
00220 if (!_isSelectable)
00221 {
00222 var index = CPNotFound;
00223
00224 while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound)
00225 [_items[index] setSelected:NO];
00226 }
00227 }
00228
00233 - (BOOL)isSelected
00234 {
00235 return _isSelected;
00236 }
00237
00242 - (void)setAllowsMultipleSelection:(BOOL)shouldAllowMultipleSelection
00243 {
00244 _allowsMultipleSelection = shouldAllowMultipleSelection;
00245 }
00246
00250 - (BOOL)allowsMultipleSelection
00251 {
00252 return _allowsMultipleSelection;
00253 }
00254
00259 - (void)setSelectionIndexes:(CPIndexSet)anIndexSet
00260 {
00261 if (_selectionIndexes == anIndexSet)
00262 return;
00263
00264 var index = CPNotFound;
00265
00266 while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound)
00267 [_items[index] setSelected:NO];
00268
00269 _selectionIndexes = anIndexSet;
00270
00271 var index = CPNotFound;
00272
00273 while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound)
00274 [_items[index] setSelected:YES];
00275
00276 if ([_delegate respondsToSelector:@selector(collectionViewDidChangeSelection:)])
00277 [_delegate collectionViewDidChangeSelection:self]
00278 }
00279
00283 - (CPIndexSet)selectionIndexes
00284 {
00285 return _selectionIndexes;
00286 }
00287
00288
00289 - (void)reloadContent
00290 {
00291
00292 var count = _items.length;
00293
00294 while (count--)
00295 {
00296 [[_items[count] view] removeFromSuperview];
00297 _cachedItems.push(_items[count]);
00298 }
00299
00300 _items = [];
00301
00302 if (!_itemData || !_content)
00303 return;
00304
00305 var index = 0;
00306
00307 count = _content.length;
00308
00309 for (; index < count; ++index)
00310 {
00311 _items.push([self newItemForRepresentedObject:_content[index]]);
00312
00313 [self addSubview:[_items[index] view]];
00314 }
00315
00316 [self tile];
00317 }
00318
00319
00320 - (void)tile
00321 {
00322 var width = CGRectGetWidth([self bounds]);
00323
00324 if (![_content count] || width == _tileWidth)
00325 return;
00326
00327
00328
00329
00330 var itemSize = CGSizeMakeCopy(_minItemSize);
00331
00332 _numberOfColumns = MAX(1.0, FLOOR(width / itemSize.width));
00333
00334 if (_maxNumberOfColumns > 0)
00335 _numberOfColumns = MIN(_maxNumberOfColumns, _numberOfColumns);
00336
00337 var remaining = width - _numberOfColumns * itemSize.width,
00338 itemsNeedSizeUpdate = NO;
00339
00340 if (remaining > 0 && itemSize.width < _maxItemSize.width)
00341 itemSize.width = MIN(_maxItemSize.width, itemSize.width + FLOOR(remaining / _numberOfColumns));
00342
00343 if (!CGSizeEqualToSize(_itemSize, itemSize))
00344 {
00345 _itemSize = itemSize;
00346 itemsNeedSizeUpdate = YES;
00347 }
00348
00349 var index = 0,
00350 count = _items.length;
00351
00352 if (_maxNumberOfColumns > 0 && _maxNumberOfRows > 0)
00353 count = MIN(count, _maxNumberOfColumns * _maxNumberOfRows);
00354
00355 _numberOfRows = CEIL(count / _numberOfColumns);
00356
00357 _horizontalMargin = FLOOR((width - _numberOfColumns * itemSize.width) / (_numberOfColumns + 1));
00358
00359 var x = _horizontalMargin,
00360 y = -itemSize.height;
00361
00362 for (; index < count; ++index)
00363 {
00364 if (index % _numberOfColumns == 0)
00365 {
00366 x = _horizontalMargin;
00367 y += _verticalMargin + itemSize.height;
00368 }
00369
00370 var view = [_items[index] view];
00371
00372 [view setFrameOrigin:CGPointMake(x, y)];
00373
00374 if (itemsNeedSizeUpdate)
00375 [view setFrameSize:_itemSize];
00376
00377 x += itemSize.width + _horizontalMargin;
00378 }
00379
00380 _tileWidth = width;
00381 [self setFrameSize:CGSizeMake(width, y + itemSize.height + _verticalMargin)];
00382 _tileWidth = -1.0;
00383 }
00384
00385 - (void)resizeSubviewsWithOldSize:(CGSize)aSize
00386 {
00387 [self tile];
00388 }
00389
00390
00395 - (void)setMaxNumberOfRows:(unsigned)aMaxNumberOfRows
00396 {
00397 if (_maxNumberOfRows == aMaxNumberOfRows)
00398 return;
00399
00400 _maxNumberOfRows = aMaxNumberOfRows;
00401
00402 [self tile];
00403 }
00404
00408 - (unsigned)maxNumberOfRows
00409 {
00410 return _maxNumberOfRows;
00411 }
00412
00417 - (void)setMaxNumberOfColumns:(unsigned)aMaxNumberOfColumns
00418 {
00419 if (_maxNumberOfColumns == aMaxNumberOfColumns)
00420 return;
00421
00422 _maxNumberOfColumns = aMaxNumberOfColumns;
00423
00424 [self tile];
00425 }
00426
00430 - (unsigned)maxNumberOfColumns
00431 {
00432 return _maxNumberOfColumns;
00433 }
00434
00439 - (void)setMinItemSize:(CGSize)aSize
00440 {
00441 if (CGSizeEqualToSize(_minItemSize, aSize))
00442 return;
00443
00444 _minItemSize = CGSizeMakeCopy(aSize);
00445
00446 [self tile];
00447 }
00448
00452 - (CGSize)minItemSize
00453 {
00454 return _minItemSize;
00455 }
00456
00461 - (void)setMaxItemSize:(CGSize)aSize
00462 {
00463 if (CGSizeEqualToSize(_maxItemSize, aSize))
00464 return;
00465
00466 _maxItemSize = CGSizeMakeCopy(aSize);
00467
00468 [self tile];
00469 }
00470
00474 - (CGSize)maxItemSize
00475 {
00476 return _maxItemSize;
00477 }
00478
00479 - (void)mouseUp:(CPEvent)anEvent
00480 {
00481 if ([_selectionIndexes count] && [anEvent clickCount] == 2 && [_delegate respondsToSelector:@selector(collectionView:didDoubleClickOnItemAtIndex:)])
00482 [_delegate collectionView:self didDoubleClickOnItemAtIndex:[_selectionIndexes firstIndex]];
00483 }
00484
00485 - (void)mouseDown:(CPEvent)anEvent
00486 {
00487 var location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
00488 row = FLOOR(location.y / (_itemSize.height + _verticalMargin)),
00489 column = FLOOR(location.x / (_itemSize.width + _horizontalMargin)),
00490 index = row * _numberOfColumns + column;
00491
00492 if (index >= 0 && index < _items.length)
00493 [self setSelectionIndexes:[CPIndexSet indexSetWithIndex:index]];
00494 }
00495
00496 - (void)mouseDragged:(CPEvent)anEvent
00497 {
00498 if (![_delegate respondsToSelector:@selector(collectionView:dragTypesForItemsAtIndexes:)])
00499 return;
00500
00501
00502 if (![_selectionIndexes count])
00503 return;
00504
00505
00506 var dragTypes = [_delegate collectionView:self dragTypesForItemsAtIndexes:_selectionIndexes];
00507
00508 [[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:dragTypes owner:self];
00509
00510 var point = [self convertPoint:[anEvent locationInWindow] fromView:nil];
00511
00512 [_itemForDragging setRepresentedObject:_content[[_selectionIndexes firstIndex]]];
00513
00514 var view = [_itemForDragging view],
00515 frame = [view frame];
00516
00517 [view setFrameSize:_itemSize];
00518
00519 [self dragView:view
00520 at:[[_items[[_selectionIndexes firstIndex]] view] frame].origin
00521 offset:CGPointMakeZero()
00522 event:anEvent
00523 pasteboard:nil
00524 source:self
00525 slideBack:YES];
00526 }
00527
00533 - (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
00534 {
00535 [aPasteboard setData:[_delegate collectionView:self dataForItemsAtIndexes:_selectionIndexes forType:aType] forType:aType];
00536 }
00537
00538
00539
00540 - (void)setVerticalMargin:(float)aVerticalMargin
00541 {
00542 if (_verticalMargin == aVerticalMargin)
00543 return;
00544
00545 _verticalMargin = aVerticalMargin;
00546
00547 [self tile];
00548 }
00549
00554 - (void)setDelegate:(id)aDelegate
00555 {
00556 _delegate = aDelegate;
00557 }
00558
00562 - (id)delegate
00563 {
00564 return _delegate;
00565 }
00566
00567 @end
00568
00572 @implementation CPCollectionViewItem : CPObject
00573 {
00574 id _representedObject;
00575
00576 CPView _view;
00577
00578 BOOL _isSelected;
00579 }
00580
00581
00586 - (void)setRepresentedObject:(id)anObject
00587 {
00588 if (_representedObject == anObject)
00589 return;
00590
00591 _representedObject = anObject;
00592
00593
00594 [_view setRepresentedObject:anObject];
00595 }
00596
00600 - (id)representedObject
00601 {
00602 return _representedObject;
00603 }
00604
00605
00610 - (void)setView:(CPView)aView
00611 {
00612 _view = aView;
00613 }
00614
00618 - (CPView)view
00619 {
00620 return _view;
00621 }
00622
00623
00628 - (void)setSelected:(BOOL)shouldBeSelected
00629 {
00630 if (_isSelected == shouldBeSelected)
00631 return;
00632
00633 _isSelected = shouldBeSelected;
00634
00635
00636 [_view setSelected:_isSelected];
00637 }
00638
00642 - (BOOL)isSelected
00643 {
00644 return _isSelected;
00645 }
00646
00647
00651 - (CPCollectionView)collectionView
00652 {
00653 return [_view superview];
00654 }
00655
00656 @end
00657
00658 var CPCollectionViewItemViewKey = @"CPCollectionViewItemViewKey";
00659
00660 @implementation CPCollectionViewItem (CPCoding)
00661
00662
00663
00664
00665 - (id)copy
00666 {
00667
00668 }
00669
00670 @end
00671
00672 var CPCollectionViewItemViewKey = @"CPCollectionViewItemViewKey";
00673
00674 @implementation CPCollectionViewItem (CPCoding)
00675
00681 - (id)initWithCoder:(CPCoder)aCoder
00682 {
00683 self = [super init];
00684
00685 if (self)
00686 _view = [aCoder decodeObjectForKey:CPCollectionViewItemViewKey];
00687
00688 return self;
00689 }
00690
00695 - (void)encodeWithCoder:(CPCoder)aCoder
00696 {
00697 [aCoder encodeObject:_view forKey:CPCollectionViewItemViewKey];
00698 }
00699
00700 @end