API  0.9.8
 All Classes Files Functions Variables Typedefs Macros Groups Pages
CPCollectionView.j
Go to the documentation of this file.
1 /*
2  * CPCollectionView.j
3  * AppKit
4  *
5  * Created by Francisco Tolmasky.
6  * Copyright 2008, 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 
35 
36 
38 
39 @optional
40 - (BOOL)collectionView:(CPCollectionView)collectionView acceptDrop:(id)draggingInfo index:(CPInteger)index dropOperation:(CPCollectionViewDropOperation)dropOperation;
41 - (BOOL)collectionView:(CPCollectionView)collectionView canDragItemsAtIndexes:(CPIndexSet)indexes withEvent:(CPEvent)event;
42 - (BOOL)collectionView:(CPCollectionView)collectionView writeItemsAtIndexes:(CPIndexSet)indexes toPasteboard:(CPPasteboard)pasteboard;
43 - (CPArray)collectionView:(CPCollectionView)collectionView dragTypesForItemsAtIndexes:(CPIndexSet)indexes;
44 - (CPData)collectionView:(CPCollectionView)collectionView dataForItemsAtIndexes:(CPIndexSet)indices forType:(CPString)aType;
45 - (CPDragOperation)collectionView:(CPCollectionView)collectionView validateDrop:(id)draggingInfo proposedIndex:(CPInteger)proposedDropIndex dropOperation:(CPCollectionViewDropOperation)proposedDropOperation;
46 - (CPMenu)collectionView:(CPCollectionView)collectionView menuForItemAtIndex:(CPInteger)anIndex;
47 - (CPView)collectionView:(CPCollectionView)collectionView dragginViewForItemsAtIndexes:(CPIndexSet)indexes withEvent:(CPEvent)event offset:(CGPoint)dragImageOffset;
48 - (void)collectionView:(CPCollectionView)collectionView didDoubleClickOnItemAtIndex:(int)index;
49 
50 @end
51 
81 
82 @implementation CPCollectionView : CPView
83 {
84  CPArray _content;
85  CPArray _items;
86 
87  CPData _itemData;
88  CPCollectionViewItem _itemPrototype;
89  CPCollectionViewItem _itemForDragging;
90  CPMutableArray _cachedItems;
91 
92  unsigned _maxNumberOfRows;
93  unsigned _maxNumberOfColumns;
94 
95  CGSize _minItemSize;
96  CGSize _maxItemSize;
97 
98  CPArray _backgroundColors;
99 
100  float _tileWidth;
101 
102  BOOL _isSelectable;
103  BOOL _allowsMultipleSelection;
104  BOOL _allowsEmptySelection;
105  CPIndexSet _selectionIndexes;
106 
107  CGSize _itemSize;
108 
109  float _horizontalMargin;
110  float _verticalMargin;
111 
112  unsigned _numberOfRows;
113  unsigned _numberOfColumns;
114 
115  id <CPCollectionViewDelegate> _delegate;
116  unsigned _implementedDelegateMethods;
117 
118  CPEvent _mouseDownEvent;
119 
120  BOOL _needsMinMaxItemSizeUpdate;
121  CGSize _storedFrameSize;
122 
123  BOOL _uniformSubviewsResizing;
124  BOOL _lockResizing;
125 
126  CPInteger _currentDropIndex;
127  CPDragOperation _currentDragOperation;
128 
129  _CPCollectionViewDropIndicator _dropView;
130 }
131 
132 - (id)initWithFrame:(CGRect)aFrame
133 {
134  self = [super initWithFrame:aFrame];
135 
136  if (self)
137  {
138  _maxNumberOfRows = 0;
139  _maxNumberOfColumns = 0;
140 
141  _minItemSize = CGSizeMakeZero();
142  _maxItemSize = CGSizeMakeZero();
143 
144  [self setBackgroundColors:nil];
145 
146  _verticalMargin = 5.0;
147  _isSelectable = YES;
148  _allowsEmptySelection = YES;
149 
150  [self _init];
151  }
152 
153  return self;
154 }
155 
156 - (void)_init
157 {
158  _content = [];
159 
160  _items = [];
161  _cachedItems = [];
162 
163  _numberOfColumns = CPNotFound;
164  _numberOfRows = CPNotFound;
165 
166  _itemSize = CGSizeMakeZero();
167 
168  _selectionIndexes = [CPIndexSet indexSet];
169 
170  _storedFrameSize = CGSizeMakeZero();
171 
172  _needsMinMaxItemSizeUpdate = YES;
173  _uniformSubviewsResizing = NO;
174  _lockResizing = NO;
175 
176  _currentDropIndex = -1;
177  _currentDragOperation = CPDragOperationNone;
178  _dropView = nil;
179 
180  [self setAutoresizesSubviews:NO];
181  [self setAutoresizingMask:0];
182 }
183 
184 
185 
186 #pragma mark -
187 #pragma mark Delegate
188 
193 - (void)setDelegate:(id <CPCollectionViewDelegate>)aDelegate
194 {
195  if (_delegate === aDelegate)
196  return;
197 
198  _delegate = aDelegate;
199  _implementedDelegateMethods = 0;
200 
201  if ([_delegate respondsToSelector:@selector(collectionView:acceptDrop:index:dropOperation:)])
203 
204  if ([_delegate respondsToSelector:@selector(collectionView:canDragItemsAtIndexes:withEvent:)])
206 
207  if ([_delegate respondsToSelector:@selector(collectionView:writeItemsAtIndexes:toPasteboard:)])
209 
210  if ([_delegate respondsToSelector:@selector(collectionView:dragTypesForItemsAtIndexes:)])
212 
213  if ([_delegate respondsToSelector:@selector(collectionView:dataForItemsAtIndexes:forType:)])
215 
216  if ([_delegate respondsToSelector:@selector(collectionView:validateDrop:proposedIndex:dropOperation:)])
218 
219  if ([_delegate respondsToSelector:@selector(collectionView:didDoubleClickOnItemAtIndex:)])
221 
222  if ([_delegate respondsToSelector:@selector(collectionView:menuForItemAtIndex:)])
224 
225  if ([_delegate respondsToSelector:@selector(collectionView:draggingViewForItemsAtIndexes:withEvent:offset:)])
227 }
228 
273 - (void)setItemPrototype:(CPCollectionViewItem)anItem
274 {
275  _cachedItems = [];
276  _itemData = nil;
277  _itemForDragging = nil;
278  _itemPrototype = anItem;
279 
280  [self _reloadContentCachingRemovedItems:NO];
281 }
282 
286 - (CPCollectionViewItem)itemPrototype
287 {
288  return _itemPrototype;
289 }
290 
295 - (CPCollectionViewItem)newItemForRepresentedObject:(id)anObject
296 {
297  var item = nil;
298 
299  if (_cachedItems.length)
300  item = _cachedItems.pop();
301 
302  else
303  item = [_itemPrototype copy];
304 
305  [item setRepresentedObject:anObject];
306  [[item view] setFrameSize:_itemSize];
307 
308  return item;
309 }
310 
311 // Working with the Responder Chain
315 - (BOOL)acceptsFirstResponder
316 {
317  return YES;
318 }
319 
323 - (BOOL)isFirstResponder
324 {
325  return [[self window] firstResponder] === self;
326 }
327 
328 // Setting the Content
340 - (void)setContent:(CPArray)anArray
341 {
342  _content = anArray;
343 
344  [self reloadContent];
345 }
346 
350 - (CPArray)content
351 {
352  return _content;
353 }
354 
358 - (CPArray)items
359 {
360  return _items;
361 }
362 
363 // Setting the Selection Mode
368 - (void)setSelectable:(BOOL)isSelectable
369 {
370  if (_isSelectable == isSelectable)
371  return;
372 
373  _isSelectable = isSelectable;
374 
375  if (!_isSelectable)
376  {
377  var index = CPNotFound,
378  itemCount = [_items count];
379 
380  // Be wary of invalid selection ranges since setContent: does not clear selection indexes.
381  while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound && index < itemCount)
382  [_items[index] setSelected:NO];
383  }
384 }
385 
390 - (BOOL)isSelectable
391 {
392  return _isSelectable;
393 }
394 
399 - (void)setAllowsEmptySelection:(BOOL)shouldAllowEmptySelection
400 {
401  _allowsEmptySelection = shouldAllowEmptySelection;
402 }
403 
407 - (BOOL)allowsEmptySelection
408 {
409  return _allowsEmptySelection;
410 }
411 
416 - (void)setAllowsMultipleSelection:(BOOL)shouldAllowMultipleSelection
417 {
418  _allowsMultipleSelection = shouldAllowMultipleSelection;
419 }
420 
424 - (BOOL)allowsMultipleSelection
425 {
426  return _allowsMultipleSelection;
427 }
428 
433 - (void)setSelectionIndexes:(CPIndexSet)anIndexSet
434 {
435  if (!anIndexSet)
436  anIndexSet = [CPIndexSet indexSet];
437  if (!_isSelectable || [_selectionIndexes isEqual:anIndexSet])
438  return;
439 
440  var index = CPNotFound,
441  itemCount = [_items count];
442 
443  // Be wary of invalid selection ranges since setContent: does not clear selection indexes.
444  while ((index = [_selectionIndexes indexGreaterThanIndex:index]) !== CPNotFound && index < itemCount)
445  [_items[index] setSelected:NO];
446 
447  _selectionIndexes = anIndexSet;
448 
449  var index = CPNotFound;
450 
451  while ((index = [_selectionIndexes indexGreaterThanIndex:index]) !== CPNotFound)
452  [_items[index] setSelected:YES];
453 
454  var binderClass = [[self class] _binderClassForBinding:@"selectionIndexes"];
455  [[binderClass getBinding:@"selectionIndexes" forObject:self] reverseSetValueFor:@"selectionIndexes"];
456 }
457 
461 - (CPIndexSet)selectionIndexes
462 {
463  return [_selectionIndexes copy];
464 }
465 
466 - (void)reloadContent
467 {
468  [self _reloadContentCachingRemovedItems:YES];
469 }
470 
471 /* @ignore */
472 - (void)_reloadContentCachingRemovedItems:(BOOL)shouldCache
473 {
474  // Remove current views
475  var count = _items.length;
476 
477  while (count--)
478  {
479  [[_items[count] view] removeFromSuperview];
480  [_items[count] setSelected:NO];
481 
482  if (shouldCache)
483  _cachedItems.push(_items[count]);
484  }
485 
486  _items = [];
487 
488  if (!_itemPrototype)
489  return;
490 
491  var index = 0;
492 
493  count = _content.length;
494 
495  for (; index < count; ++index)
496  {
497  _items.push([self newItemForRepresentedObject:_content[index]]);
498 
499  [self addSubview:[_items[index] view]];
500  }
501 
502  index = CPNotFound;
503  // Be wary of invalid selection ranges since setContent: does not clear selection indexes.
504  while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound && index < count)
505  [_items[index] setSelected:YES];
506 
507  [self tileIfNeeded:NO];
508 }
509 
510 - (void)resizeSubviewsWithOldSize:(CGSize)oldBoundsSize
511 {
512  // Desactivate subviews autoresizing
513 }
514 
515 - (void)resizeWithOldSuperviewSize:(CGSize)oldBoundsSize
516 {
517  if (_lockResizing)
518  return;
519 
520  _lockResizing = YES;
521 
522  [self tile];
523 
524  _lockResizing = NO;
525 }
526 
527 - (void)tile
528 {
529  [self tileIfNeeded:!_uniformSubviewsResizing];
530 }
531 
532 - (void)tileIfNeeded:(BOOL)lazyFlag
533 {
534  var frameSize = [[self superview] frameSize],
535  count = _items.length,
536  oldNumberOfColumns = _numberOfColumns,
537  oldNumberOfRows = _numberOfRows,
538  oldItemSize = _itemSize,
539  storedFrameSize = _storedFrameSize;
540 
541  // No need to tile if we are not yet placed in the view hierarchy.
542  if (!frameSize)
543  return;
544 
545  [self _updateMinMaxItemSizeIfNeeded];
546 
547  [self _computeGridWithSize:frameSize count:@ref(count)];
548 
549  //CPLog.debug("frameSize="+CPStringFromSize(frameSize) + "itemSize="+CPStringFromSize(itemSize) + " ncols=" + colsRowsCount[0] +" nrows="+ colsRowsCount[1]+" displayCount="+ colsRowsCount[2]);
550 
551  [self setFrameSize:_storedFrameSize];
552 
553  //CPLog.debug("OLD " + oldNumberOfColumns + " NEW " + _numberOfColumns);
554  if (!lazyFlag ||
555  _numberOfColumns !== oldNumberOfColumns ||
556  _numberOfRows !== oldNumberOfRows ||
557  !CGSizeEqualToSize(_itemSize, oldItemSize))
558 
559  [self displayItems:_items frameSize:_storedFrameSize itemSize:_itemSize columns:_numberOfColumns rows:_numberOfRows count:count];
560 }
561 
562 - (void)_computeGridWithSize:(CGSize)aSuperviewSize count:(Function)countRef
563 {
564  var width = aSuperviewSize.width,
565  height = aSuperviewSize.height,
566  itemSize = CGSizeMakeCopy(_minItemSize),
567  maxItemSizeWidth = _maxItemSize.width,
568  maxItemSizeHeight = _maxItemSize.height,
569  itemsCount = [_items count],
570  numberOfRows,
571  numberOfColumns;
572 
573  numberOfColumns = FLOOR(width / itemSize.width);
574 
575  if (maxItemSizeWidth == 0)
576  numberOfColumns = MIN(numberOfColumns, _maxNumberOfColumns);
577 
578  if (_maxNumberOfColumns > 0)
579  numberOfColumns = MIN(MIN(_maxNumberOfColumns, itemsCount), numberOfColumns);
580 
581  numberOfColumns = MAX(1.0, numberOfColumns);
582 
583  itemSize.width = FLOOR(width / numberOfColumns);
584 
585  if (maxItemSizeWidth > 0)
586  {
587  itemSize.width = MIN(maxItemSizeWidth, itemSize.width);
588 
589  if (numberOfColumns == 1)
590  itemSize.width = MIN(maxItemSizeWidth, width);
591  }
592 
593  numberOfRows = CEIL(itemsCount / numberOfColumns);
594 
595  if (_maxNumberOfRows > 0)
596  numberOfRows = MIN(numberOfRows, _maxNumberOfRows);
597 
598  height = MAX(height, numberOfRows * (_minItemSize.height + _verticalMargin));
599 
600  var itemSizeHeight = FLOOR(height / numberOfRows) - _verticalMargin;
601 
602  if (maxItemSizeHeight > 0)
603  itemSizeHeight = MIN(itemSizeHeight, maxItemSizeHeight);
604 
605  _itemSize = CGSizeMake(MAX(_minItemSize.width, itemSize.width), MAX(_minItemSize.height, itemSizeHeight));
606  _storedFrameSize = CGSizeMake(MAX(width, _minItemSize.width), height);
607  _numberOfColumns = numberOfColumns;
608  _numberOfRows = numberOfRows;
609  countRef(MIN(itemsCount, numberOfColumns * numberOfRows));
610 }
611 
612 - (void)displayItems:(CPArray)displayItems frameSize:(CGSize)aFrameSize itemSize:(CGSize)anItemSize columns:(CPInteger)numberOfColumns rows:(CPInteger)numberOfRows count:(CPInteger)displayCount
613 {
614 // CPLog.debug("DISPLAY ITEMS " + numberOfColumns + " " + numberOfRows);
615 
616  _horizontalMargin = _uniformSubviewsResizing ? FLOOR((aFrameSize.width - numberOfColumns * anItemSize.width) / (numberOfColumns + 1)) : HORIZONTAL_MARGIN;
617 
618  var x = _horizontalMargin,
619  y = -anItemSize.height;
620 
621  [displayItems enumerateObjectsUsingBlock:function(item, idx, stop)
622  {
623  var view = [item view];
624 
625  if (idx >= displayCount)
626  {
627  [view setFrameOrigin:CGPointMake(-anItemSize.width, -anItemSize.height)];
628  return;
629  }
630 
631  if (idx % numberOfColumns == 0)
632  {
633  x = _horizontalMargin;
634  y += _verticalMargin + anItemSize.height;
635  }
636 
637  [view setFrameOrigin:CGPointMake(x, y)];
638  [view setFrameSize:anItemSize];
639 
640  x += anItemSize.width + _horizontalMargin;
641  }];
642 }
643 
644 - (void)_updateMinMaxItemSizeIfNeeded
645 {
646  if (!_needsMinMaxItemSizeUpdate)
647  return;
648 
649  var prototypeView;
650 
651  if (_itemPrototype && (prototypeView = [_itemPrototype view]))
652  {
653  if (_minItemSize.width == 0)
654  _minItemSize.width = [prototypeView frameSize].width;
655 
656  if (_minItemSize.height == 0)
657  _minItemSize.height = [prototypeView frameSize].height;
658 
659  if (_maxItemSize.height == 0 && !([prototypeView autoresizingMask] & CPViewHeightSizable))
660  _maxItemSize.height = [prototypeView frameSize].height;
661 
662  if (_maxItemSize.width == 0 && !([prototypeView autoresizingMask] & CPViewWidthSizable))
663  _maxItemSize.width = [prototypeView frameSize].width;
664 
665  _needsMinMaxItemSizeUpdate = NO;
666  }
667 }
668 
669 // Laying Out the Collection View
674 - (void)setMaxNumberOfRows:(unsigned)aMaxNumberOfRows
675 {
676  if (_maxNumberOfRows == aMaxNumberOfRows)
677  return;
678 
679  _maxNumberOfRows = aMaxNumberOfRows;
680 
681  [self tile];
682 }
683 
687 - (unsigned)maxNumberOfRows
688 {
689  return _maxNumberOfRows;
690 }
691 
696 - (void)setMaxNumberOfColumns:(unsigned)aMaxNumberOfColumns
697 {
698  if (_maxNumberOfColumns == aMaxNumberOfColumns)
699  return;
700 
701  _maxNumberOfColumns = aMaxNumberOfColumns;
702 
703  [self tile];
704 }
705 
709 - (unsigned)maxNumberOfColumns
710 {
711  return _maxNumberOfColumns;
712 }
713 
717 - (unsigned)numberOfRows
718 {
719  return _numberOfRows;
720 }
721 
726 - (unsigned)numberOfColumns
727 {
728  return _numberOfColumns;
729 }
730 
735 - (void)setMinItemSize:(CGSize)aSize
736 {
737  if (aSize === nil || aSize === undefined)
738  [CPException raise:CPInvalidArgumentException reason:"Invalid value provided for minimum size"];
739 
740  if (CGSizeEqualToSize(_minItemSize, aSize))
741  return;
742 
743  _minItemSize = CGSizeMakeCopy(aSize);
744 
745  if (CGSizeEqualToSize(_minItemSize, CGSizeMakeZero()))
746  _needsMinMaxItemSizeUpdate = YES;
747 
748  [self tile];
749 }
750 
754 - (CGSize)minItemSize
755 {
756  return _minItemSize;
757 }
758 
763 - (void)setMaxItemSize:(CGSize)aSize
764 {
765  if (CGSizeEqualToSize(_maxItemSize, aSize))
766  return;
767 
768  _maxItemSize = CGSizeMakeCopy(aSize);
769 
770 // if (_maxItemSize.width == 0 || _maxItemSize.height == 0)
771 // _needsMinMaxItemSizeUpdate = YES;
772 
773  [self tile];
774 }
775 
779 - (CGSize)maxItemSize
780 {
781  return _maxItemSize;
782 }
783 
784 - (void)setBackgroundColors:(CPArray)backgroundColors
785 {
786  if (_backgroundColors === backgroundColors)
787  return;
788 
789  _backgroundColors = backgroundColors;
790 
791  if (!_backgroundColors)
792  _backgroundColors = [[CPColor whiteColor]];
793 
794  if ([_backgroundColors count] === 1)
795  [self setBackgroundColor:_backgroundColors[0]];
796 
797  else
798  [self setBackgroundColor:nil];
799 
800  [self setNeedsDisplay:YES];
801 }
802 
803 - (CPArray)backgroundColors
804 {
805  return _backgroundColors;
806 }
807 
808 - (void)mouseUp:(CPEvent)anEvent
809 {
810  if ([_selectionIndexes count] && [anEvent clickCount] == 2)
811  [self _sendDelegateDidDoubleClickOnItemAtIndex:[_selectionIndexes firstIndex]];
812 }
813 
814 - (void)mouseDown:(CPEvent)anEvent
815 {
816  _mouseDownEvent = anEvent;
817 
818  var location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
819  index = [self _indexAtPoint:location];
820 
821  if (index >= 0 && index < _items.length)
822  {
823  if (_allowsMultipleSelection && ([anEvent modifierFlags] & CPPlatformActionKeyMask || [anEvent modifierFlags] & CPShiftKeyMask))
824  {
825  if ([anEvent modifierFlags] & CPPlatformActionKeyMask)
826  {
827  var indexes = [_selectionIndexes copy];
828 
829  if ([indexes containsIndex:index])
830  [indexes removeIndex:index];
831  else
832  [indexes addIndex:index];
833  }
834  else if ([anEvent modifierFlags] & CPShiftKeyMask)
835  {
836  var firstSelectedIndex = [[self selectionIndexes] firstIndex],
837  newSelectedRange = nil;
838 
839  // This catches the case where the shift key is held down for the first selection.
840  if (firstSelectedIndex === CPNotFound)
841  firstSelectedIndex = index;
842 
843  if (index < firstSelectedIndex)
844  newSelectedRange = CPMakeRange(index, (firstSelectedIndex - index) + 1);
845  else
846  newSelectedRange = CPMakeRange(firstSelectedIndex, (index - firstSelectedIndex) + 1);
847 
848  indexes = [[self selectionIndexes] copy];
849  [indexes addIndexesInRange:newSelectedRange];
850  }
851  }
852  else
853  indexes = [CPIndexSet indexSetWithIndex:index];
854 
855  [self setSelectionIndexes:indexes];
856 
857  // TODO Is it allowable for collection view items to become the first responder? In that case they
858  // may have become that at this point by virtue of CPWindow's sendEvent: mouse down handling, and
859  // the following line will rudely snatch it away from them. For most cases though, clicking on an
860  // item should naturally make the collection view the first responder so that keyboard navigation
861  // is enabled.
862  [[self window] makeFirstResponder:self];
863  }
864  else if (_allowsEmptySelection)
866 }
867 
868 // Cappuccino Additions
869 
875 - (void)setVerticalMargin:(float)aVerticalMargin
876 {
877  if (_verticalMargin == aVerticalMargin)
878  return;
879 
880  _verticalMargin = aVerticalMargin;
881 
882  [self tile];
883 }
884 
885 - (void)setUniformSubviewsResizing:(BOOL)flag
886 {
887  _uniformSubviewsResizing = flag;
888  [self tileIfNeeded:NO];
889 }
890 
891 
896 - (float)verticalMargin
897 {
898  return _verticalMargin;
899 }
900 
904 - (id)delegate
905 {
906  return _delegate;
907 }
908 
912 - (CPMenu)menuForEvent:(CPEvent)theEvent
913 {
914  if (![self _delegateRespondsToCollectionViewMenuForItemAtIndex])
915  return [super menuForEvent:theEvent];
916 
917  var location = [self convertPoint:[theEvent locationInWindow] fromView:nil],
918  index = [self _indexAtPoint:location];
919 
920  return [self _sendDelegateMenuForItemAtIndex:index];
921 }
922 
923 - (int)_indexAtPoint:(CGPoint)thePoint
924 {
925  var column = FLOOR(thePoint.x / (_itemSize.width + _horizontalMargin));
926 
927  if (column < _numberOfColumns)
928  {
929  var row = FLOOR(thePoint.y / (_itemSize.height + _verticalMargin));
930 
931  if (row < _numberOfRows)
932  return (row * _numberOfColumns + column);
933  }
934 
935  return CPNotFound;
936 }
937 
938 - (CPCollectionViewItem)itemAtIndex:(CPUInteger)anIndex
939 {
940  return [_items objectAtIndex:anIndex];
941 }
942 
943 - (CGRect)frameForItemAtIndex:(CPUInteger)anIndex
944 {
945  return [[[self itemAtIndex:anIndex] view] frame];
946 }
947 
948 - (CGRect)frameForItemsAtIndexes:(CPIndexSet)anIndexSet
949 {
950  var indexArray = [],
951  frame = CGRectNull;
952 
953  [anIndexSet getIndexes:indexArray maxCount:-1 inIndexRange:nil];
954 
955  var index = 0,
956  count = [indexArray count];
957 
958  for (; index < count; ++index)
959  frame = CGRectUnion(frame, [self frameForItemAtIndex:indexArray[index]]);
960 
961  return frame;
962 }
963 
964 @end
965 
967 /*
968  TODO: dropOperation is not supported yet. The visible drop operation is like CPCollectionViewDropBefore.
969 */
970 
976 - (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
977 {
978  [aPasteboard setData:[self _sendDelegateDataForItemsAtIndexes:_selectionIndexes forType:aType] forType:aType];
979 }
980 
981 - (void)_createDropIndicatorIfNeeded
982 {
983  // Create and position the drop indicator view.
984 
985  if (!_dropView)
986  _dropView = [[_CPCollectionViewDropIndicator alloc] initWithFrame:CGRectMake(-8, -8, 0, 0)];
987 
988  [_dropView setFrameSize:CGSizeMake(10, _itemSize.height + _verticalMargin)];
989  [self addSubview:_dropView];
990 }
991 
992 - (void)mouseDragged:(CPEvent)anEvent
993 {
994  // Don't crash if we never registered the intial click.
995  if (!_mouseDownEvent)
996  return;
997 
998  [self _createDropIndicatorIfNeeded];
999 
1000  var locationInWindow = [anEvent locationInWindow],
1001  mouseDownLocationInWindow = [_mouseDownEvent locationInWindow];
1002 
1003  // FIXME: This is because Safari's drag hysteresis is 3px x 3px
1004  if ((ABS(locationInWindow.x - mouseDownLocationInWindow.x) < 3) &&
1005  (ABS(locationInWindow.y - mouseDownLocationInWindow.y) < 3))
1006  return;
1007 
1008  if (![self _delegateRespondsToCollectionViewDragTypesForItemsAtIndexes])
1009  return;
1010 
1011  // If we don't have any selected items, we've clicked away, and thus the drag is meaningless.
1012  if (![_selectionIndexes count])
1013  return;
1014 
1015  if (![self _sendDelegateCanDragItemsAtIndexes:_selectionIndexes withEvent:_mouseDownEvent])
1016  return;
1017 
1018  // Set up the pasteboard
1019  var dragTypes = [self _sendDelegateDragTypesForItemsAtIndexes:_selectionIndexes];
1020 
1021  [[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:dragTypes owner:self];
1022 
1023  var dragImageOffset = CGSizeMakeZero(),
1024  view = [self _sendDelegateDraggingViewForItemsAtIndexes:_selectionIndexes withEvent:_mouseDownEvent offset:dragImageOffset];
1025 
1026  [view setFrameSize:_itemSize];
1027  [view setAlphaValue:0.7];
1028 
1029  var dragLocation = [self convertPoint:locationInWindow fromView:nil],
1030  dragPoint = CGPointMake(dragLocation.x - _itemSize.width / 2 , dragLocation.y - _itemSize.height / 2);
1031 
1032  [self dragView:view
1033  at:dragPoint
1034  offset:dragImageOffset
1035  event:_mouseDownEvent
1036  pasteboard:nil
1037  source:self
1038  slideBack:YES];
1039 }
1040 
1041 - (CPView)draggingViewForItemsAtIndexes:(CPIndexSet)indexes withEvent:(CPEvent)event offset:(CGPoint)dragImageOffset
1042 {
1043  var idx = _content[[indexes firstIndex]];
1044 
1045  if (!_itemForDragging)
1046  _itemForDragging = [self newItemForRepresentedObject:idx];
1047  else
1048  [_itemForDragging setRepresentedObject:idx];
1049 
1050  return [_itemForDragging view];
1051 }
1052 
1053 - (CPDragOperation)draggingEntered:(id)draggingInfo
1054 {
1055  var dropIndex = -1,
1056  dropIndexRef = @ref(dropIndex),
1057  dragOp = [self _validateDragWithInfo:draggingInfo dropIndex:dropIndexRef dropOperation:1];
1058 
1059  dropIndex = dropIndexRef();
1060 
1061  [self _createDropIndicatorIfNeeded];
1062 
1063  [self _updateDragAndDropStateWithDraggingInfo:draggingInfo newDragOperation:dragOp newDropIndex:dropIndex newDropOperation:1];
1064 
1065  return _currentDragOperation;
1066 }
1067 
1068 - (CPDragOperation)draggingUpdated:(id)draggingInfo
1069 {
1070  if (![self _dropIndexDidChange:draggingInfo])
1071  return _currentDragOperation;
1072 
1073  var dropIndex,
1074  dropIndexRef = @ref(dropIndex);
1075 
1076  var dragOperation = [self _validateDragWithInfo:draggingInfo dropIndex:dropIndexRef dropOperation:1];
1077 
1078  dropIndex = dropIndexRef();
1079 
1080  [self _updateDragAndDropStateWithDraggingInfo:draggingInfo newDragOperation:dragOperation newDropIndex:dropIndex newDropOperation:1];
1081 
1082  return dragOperation;
1083 }
1084 
1085 - (CPDragOperation)_validateDragWithInfo:(id)draggingInfo dropIndex:(Function)dropIndexRef dropOperation:(int)dropOperation
1086 {
1087  var result = CPDragOperationMove,
1088  dropIndex = [self _dropIndexForDraggingInfo:draggingInfo proposedDropOperation:dropOperation];
1089 
1090  if ([self _delegateRespondsToCollectionViewValidateDropProposedIndexDropOperation])
1091  {
1092  var dropIndexRef2 = @ref(dropIndex);
1093 
1094  result = [self _sendDelegateValidateDrop:draggingInfo proposedIndex:dropIndexRef2 dropOperation:dropOperation];
1095 
1096  if (result !== CPDragOperationNone)
1097  dropIndex = dropIndexRef2();
1098  }
1099 
1100  dropIndexRef(dropIndex);
1101 
1102  return result;
1103 }
1104 
1105 - (void)draggingExited:(id)draggingInfo
1106 {
1107  [self _updateDragAndDropStateWithDraggingInfo:draggingInfo newDragOperation:0 newDropIndex:-1 newDropOperation:1];
1108 }
1109 
1110 - (void)draggingEnded:(id)draggingInfo
1111 {
1112  [self _updateDragAndDropStateWithDraggingInfo:draggingInfo newDragOperation:0 newDropIndex:-1 newDropOperation:1];
1113 }
1114 
1115 /*
1116 Not supported. Use -collectionView:dataForItemsAtIndexes:fortype:
1117 - (BOOL)_writeItemsAtIndexes:(CPIndexSet)indexes toPasteboard:(CPPasteboard)pboard
1118 {
1119  if ([self respondsToSelector:@selector(collectionView:writeItemsAtIndexes:toPasteboard:)])
1120  return [_delegate collectionView:self writeItemsAtIndexes:indexes toPasteboard:pboard];
1121 
1122  return NO;
1123 }
1124 */
1125 
1126 - (BOOL)performDragOperation:(id)draggingInfo
1127 {
1128  var result = NO;
1129 
1130  if (_currentDragOperation && _currentDropIndex !== -1)
1131  result = [self _sendDelegateAcceptDrop:draggingInfo index:_currentDropIndex dropOperation:1];
1132 
1133  [self draggingEnded:draggingInfo]; // Is this correct ?
1134 
1135  return result;
1136 }
1137 
1138 - (void)_updateDragAndDropStateWithDraggingInfo:(id)draggingInfo newDragOperation:(CPDragOperation)dragOperation newDropIndex:(CPInteger)dropIndex newDropOperation:(CPInteger)dropOperation
1139 {
1140  _currentDropIndex = dropIndex;
1141  _currentDragOperation = dragOperation;
1142 
1143  var frameOrigin,
1144  dropviewFrameWidth = CGRectGetWidth([_dropView frame]);
1145 
1146  if (_currentDropIndex == -1 || _currentDragOperation == CPDragOperationNone)
1147  frameOrigin = CGPointMake(-dropviewFrameWidth, 0);
1148  else if (_currentDropIndex == 0)
1149  frameOrigin = CGPointMake(0, 0);
1150  else
1151  {
1152  var offset;
1153 
1154  if ((_currentDropIndex % _numberOfColumns) !== 0 || _currentDropIndex == [_items count])
1155  {
1156  dropIndex = _currentDropIndex - 1;
1157  offset = (_horizontalMargin - dropviewFrameWidth) / 2;
1158  }
1159  else
1160  {
1161  offset = - _itemSize.width - dropviewFrameWidth - (_horizontalMargin - dropviewFrameWidth) / 2;
1162  }
1163 
1164  var rect = [self frameForItemAtIndex:dropIndex];
1165 
1166  frameOrigin = CGPointMake(CGRectGetMaxX(rect) + offset, rect.origin.y - _verticalMargin);
1167  }
1168 
1169  [_dropView setFrameOrigin:frameOrigin];
1170 }
1171 
1172 - (BOOL)_dropIndexDidChange:(id)draggingInfo
1173 {
1174  var dropIndex = [self _dropIndexForDraggingInfo:draggingInfo proposedDropOperation:1];
1175 
1176  if (dropIndex == CPNotFound)
1177  dropIndex = [[self content] count];
1178 
1179  return (_currentDropIndex !== dropIndex)
1180 }
1181 
1182 - (CPInteger)_dropIndexForDraggingInfo:(id)draggingInfo proposedDropOperation:(int)dropOperation
1183 {
1184  var location = [self convertPoint:[draggingInfo draggingLocation] fromView:nil],
1185  locationX = location.x + _itemSize.width / 2;
1186 
1187  var column = MIN(FLOOR(locationX / (_itemSize.width + _horizontalMargin)), _numberOfColumns),
1188  row = FLOOR(location.y / (_itemSize.height + _verticalMargin));
1189 
1190  if (row >= _numberOfRows - 1)
1191  {
1192  if (row >= _numberOfRows)
1193  {
1194  row = _numberOfRows - 1;
1195  column = _numberOfColumns;
1196  }
1197 
1198  return MIN((row * _numberOfColumns + column), [_items count]);
1199  }
1200 
1201  return (row * _numberOfColumns + column);
1202 }
1203 
1204 @end
1205 @implementation _CPCollectionViewDropIndicator : CPView
1206 {
1207  id __doxygen__;
1208 }
1209 
1210 - (void)drawRect:(CGRect)aRect
1211 {
1212  var context = [[CPGraphicsContext currentContext] graphicsPort],
1213  width = CGRectGetWidth(aRect),
1214  circleRect = CGRectMake(1, 1, width - 2, width - 2);
1215 
1216  CGContextSetStrokeColor(context, [CPColor colorWithHexString:@"4886ca"]);
1217  CGContextSetFillColor(context, [CPColor whiteColor]);
1218  CGContextSetLineWidth(context, 3);
1219 
1220  //draw white under the circle thing
1221  CGContextFillRect(context, circleRect);
1222 
1223  //draw the circle thing
1224  CGContextStrokeEllipseInRect(context, circleRect);
1225 
1226  //then draw the line
1227  CGContextBeginPath(context);
1228  CGContextMoveToPoint(context, FLOOR(width / 2), CGRectGetMinY(aRect) + width);
1229  CGContextAddLineToPoint(context, FLOOR(width / 2), CGRectGetHeight(aRect));
1230  CGContextStrokePath(context);
1231 }
1232 
1233 @end
1234 
1236 
1237 - (void)_modifySelectionWithNewIndex:(int)anIndex direction:(int)aDirection expand:(BOOL)shouldExpand
1238 {
1239  var count = [[self items] count];
1240 
1241  if (count === 0)
1242  return;
1243 
1244  anIndex = MIN(MAX(anIndex, 0), count - 1);
1245 
1246  if (_allowsMultipleSelection && shouldExpand)
1247  {
1248  var indexes = [_selectionIndexes copy],
1249  bottomAnchor = [indexes firstIndex],
1250  topAnchor = [indexes lastIndex];
1251 
1252  // if the direction is backward (-1) check with the bottom anchor
1253  if (aDirection === -1)
1254  [indexes addIndexesInRange:CPMakeRange(anIndex, bottomAnchor - anIndex + 1)];
1255  else
1256  [indexes addIndexesInRange:CPMakeRange(topAnchor, anIndex - topAnchor + 1)];
1257  }
1258  else
1259  indexes = [CPIndexSet indexSetWithIndex:anIndex];
1260 
1261  [self setSelectionIndexes:indexes];
1262  [self _scrollToSelection];
1263 }
1264 
1265 - (void)_scrollToSelection
1266 {
1267  var frame = [self frameForItemsAtIndexes:[self selectionIndexes]];
1268 
1269  if (!CGRectIsEmpty(frame))
1270  [self scrollRectToVisible:frame];
1271 }
1272 
1273 - (void)moveLeft:(id)sender
1274 {
1275  var index = [[self selectionIndexes] firstIndex];
1276  if (index === CPNotFound)
1277  index = [[self items] count];
1278 
1279  [self _modifySelectionWithNewIndex:index - 1 direction:-1 expand:NO];
1280 }
1281 
1282 - (void)moveLeftAndModifySelection:(id)sender
1283 {
1284  var index = [[self selectionIndexes] firstIndex];
1285  if (index === CPNotFound)
1286  index = [[self items] count];
1287 
1288  [self _modifySelectionWithNewIndex:index - 1 direction:-1 expand:YES];
1289 }
1290 
1291 - (void)moveRight:(id)sender
1292 {
1293  [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + 1 direction:1 expand:NO];
1294 }
1295 
1296 - (void)moveRightAndModifySelection:(id)sender
1297 {
1298  [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + 1 direction:1 expand:YES];
1299 }
1300 
1301 - (void)moveDown:(id)sender
1302 {
1303  [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + [self numberOfColumns] direction:1 expand:NO];
1304 }
1305 
1306 - (void)moveDownAndModifySelection:(id)sender
1307 {
1308  [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + [self numberOfColumns] direction:1 expand:YES];
1309 }
1310 
1311 - (void)moveUp:(id)sender
1312 {
1313  var index = [[self selectionIndexes] firstIndex];
1314  if (index == CPNotFound)
1315  index = [[self items] count];
1316 
1317  [self _modifySelectionWithNewIndex:index - [self numberOfColumns] direction:-1 expand:NO];
1318 }
1319 
1320 - (void)moveUpAndModifySelection:(id)sender
1321 {
1322  var index = [[self selectionIndexes] firstIndex];
1323  if (index == CPNotFound)
1324  index = [[self items] count];
1325 
1326  [self _modifySelectionWithNewIndex:index - [self numberOfColumns] direction:-1 expand:YES];
1327 }
1328 
1329 - (void)deleteBackward:(id)sender
1330 {
1331  if ([[self delegate] respondsToSelector:@selector(collectionView:shouldDeleteItemsAtIndexes:)])
1332  {
1333  [[self delegate] collectionView:self shouldDeleteItemsAtIndexes:[self selectionIndexes]];
1334 
1335  var index = [[self selectionIndexes] firstIndex];
1336  if (index > [[self content] count] - 1)
1337  [self setSelectionIndexes:[CPIndexSet indexSetWithIndex:[[self content] count] - 1]];
1338 
1339  [self _scrollToSelection];
1340  [self setNeedsDisplay:YES];
1341  }
1342 }
1343 
1344 - (void)keyDown:(CPEvent)anEvent
1345 {
1346  [self interpretKeyEvents:[anEvent]];
1347 }
1348 
1349 - (void)setAutoresizingMask:(unsigned)aMask
1350 {
1351  [super setAutoresizingMask:0];
1352 }
1353 
1354 @end
1355 
1356 
1358 
1359 - (CGRect)rectForItemAtIndex:(int)anIndex
1360 {
1361  _CPReportLenientDeprecation([self class], _cmd, @selector(frameForItemAtIndex:));
1362 
1363  // Don't re-compute anything just grab the current frame
1364  // This allows subclasses to override tile without messing this up.
1365  return [self frameForItemAtIndex:anIndex];
1366 }
1367 
1368 - (CGRect)rectForItemsAtIndexes:(CPIndexSet)anIndexSet
1369 {
1370  _CPReportLenientDeprecation([self class], _cmd, @selector(frameForItemsAtIndexes:));
1371 
1372  return [self frameForItemsAtIndexes:anIndexSet];
1373 }
1374 
1375 @end
1376 
1377 
1379 
1380 /*
1381  @ignore
1382  Return YES if the delegate implements collectionView:validateDrop:proposedIndex:dropOperation:
1383 */
1384 - (BOOL)_delegateRespondsToCollectionViewValidateDropProposedIndexDropOperation
1385 {
1387 }
1388 
1389 /*
1390  @ignore
1391  Return YES if the delegate implements collectionView:menuForItemAtIndex:
1392 */
1393 - (BOOL)_delegateRespondsToCollectionViewMenuForItemAtIndex
1394 {
1395  return _implementedDelegateMethods & CPCollectionViewDelegate_collectionView_menuForItemAtIndex_;
1396 }
1397 
1398 /*
1399  @ignore
1400  Return YES if the delegate implements collectionView:dragTypesForItemsAtIndexes:
1401 */
1402 - (BOOL)_delegateRespondsToCollectionViewDragTypesForItemsAtIndexes
1403 {
1404  return _implementedDelegateMethods & CPCollectionViewDelegate_collectionView_dragTypesForItemsAtIndexes_;
1405 }
1406 
1411 - (BOOL)_sendDelegateAcceptDrop:(id)draggingInfo index:(CPInteger)index dropOperation:(CPCollectionViewDropOperation)dropOperation
1412 {
1414  return NO;
1415 
1416  return [_delegate collectionView:self acceptDrop:draggingInfo index:index dropOperation:dropOperation];
1417 }
1418 
1423 - (BOOL)_sendDelegateCanDragItemsAtIndexes:(CPIndexSet)indexes withEvent:(CPEvent)anEvent
1424 {
1426  return YES;
1427 
1428  return [_delegate collectionView:self canDragItemsAtIndexes:indexes withEvent:anEvent];
1429 }
1430 
1435 - (BOOL)_sendDelegateWriteItemsAtIndexes:(CPIndexSet)indexes toPasteboard:(CPPasteboard)pasteboard
1436 {
1438  return NO;
1439 
1440  return [_delegate collectionView:self writeItemsAtIndexes:indexes toPasteboard:pasteboard];
1441 }
1442 
1447 - (CPArray)_sendDelegateDragTypesForItemsAtIndexes:(CPIndexSet)indexes
1448 {
1449  if (!(_implementedDelegateMethods & CPCollectionViewDelegate_collectionView_dragTypesForItemsAtIndexes_))
1450  return [];
1451 
1452  return [_delegate collectionView:self dragTypesForItemsAtIndexes:indexes];
1453 }
1454 
1459 - (CPData)_sendDelegateDataForItemsAtIndexes:(CPIndexSet)indexes forType:(CPString)aType
1460 {
1462  return nil;
1463 
1464  return [_delegate collectionView:self dataForItemsAtIndexes:indexes forType:aType];
1465 }
1466 
1471 - (CPDragOperation)_sendDelegateValidateDrop:(id)draggingInfo proposedIndex:(CPInteger)proposedDropIndex dropOperation:(CPCollectionViewDropOperation)proposedDropOperation
1472 {
1474  return CPDragOperationNone;
1475 
1476  return [_delegate collectionView:self validateDrop:draggingInfo proposedIndex:proposedDropIndex dropOperation:proposedDropOperation];
1477 }
1478 
1483 - (void)_sendDelegateDidDoubleClickOnItemAtIndex:(int)index
1484 {
1485  if (!(_implementedDelegateMethods & CPCollectionViewDelegate_collectionView_didDoubleClickOnItemAtIndex_))
1486  return;
1487 
1488  return [_delegate collectionView:self didDoubleClickOnItemAtIndex:index];
1489 }
1490 
1491 
1496 - (void)_sendDelegateMenuForItemAtIndex:(CPInteger)anIndex
1497 {
1498  if (!(_implementedDelegateMethods & CPCollectionViewDelegate_collectionView_menuForItemAtIndex_))
1499  return nil;
1500 
1501  return [_delegate collectionView:self menuForItemAtIndex:anIndex];
1502 }
1503 
1508 - (CPView)_sendDelegateDraggingViewForItemsAtIndexes:(CPIndexSet)indexes withEvent:(CPEvent)anEvent offset:(CGPoint)dragImageOffset
1509 {
1511  return [self draggingViewForItemsAtIndexes:indexes withEvent:anEvent offset:dragImageOffset];
1512 
1513  return [_delegate collectionView:self draggingViewForItemsAtIndexes:indexes withEvent:anEvent offset:dragImageOffset];
1514 }
1515 
1516 @end
1517 
1518 
1519 var CPCollectionViewMinItemSizeKey = @"CPCollectionViewMinItemSizeKey",
1520  CPCollectionViewMaxItemSizeKey = @"CPCollectionViewMaxItemSizeKey",
1521  CPCollectionViewVerticalMarginKey = @"CPCollectionViewVerticalMarginKey",
1522  CPCollectionViewMaxNumberOfRowsKey = @"CPCollectionViewMaxNumberOfRowsKey",
1523  CPCollectionViewMaxNumberOfColumnsKey = @"CPCollectionViewMaxNumberOfColumnsKey",
1524  CPCollectionViewSelectableKey = @"CPCollectionViewSelectableKey",
1525  CPCollectionViewAllowsMultipleSelectionKey = @"CPCollectionViewAllowsMultipleSelectionKey",
1526  CPCollectionViewBackgroundColorsKey = @"CPCollectionViewBackgroundColorsKey";
1527 
1528 
1530 
1531 - (id)initWithCoder:(CPCoder)aCoder
1532 {
1533  self = [super initWithCoder:aCoder];
1534 
1535  if (self)
1536  {
1537  _minItemSize = [aCoder decodeSizeForKey:CPCollectionViewMinItemSizeKey];
1538  _maxItemSize = [aCoder decodeSizeForKey:CPCollectionViewMaxItemSizeKey];
1539 
1540  _maxNumberOfRows = [aCoder decodeIntForKey:CPCollectionViewMaxNumberOfRowsKey];
1541  _maxNumberOfColumns = [aCoder decodeIntForKey:CPCollectionViewMaxNumberOfColumnsKey];
1542 
1543  _verticalMargin = [aCoder decodeFloatForKey:CPCollectionViewVerticalMarginKey];
1544 
1545  _isSelectable = [aCoder decodeBoolForKey:CPCollectionViewSelectableKey];
1546  _allowsMultipleSelection = [aCoder decodeBoolForKey:CPCollectionViewAllowsMultipleSelectionKey];
1547 
1548  [self setBackgroundColors:[aCoder decodeObjectForKey:CPCollectionViewBackgroundColorsKey]];
1549 
1550  [self _init];
1551  }
1552 
1553  return self;
1554 }
1555 
1556 - (void)encodeWithCoder:(CPCoder)aCoder
1557 {
1558  [super encodeWithCoder:aCoder];
1559 
1560  if (!CGSizeEqualToSize(_minItemSize, CGSizeMakeZero()))
1561  [aCoder encodeSize:_minItemSize forKey:CPCollectionViewMinItemSizeKey];
1562 
1563  if (!CGSizeEqualToSize(_maxItemSize, CGSizeMakeZero()))
1564  [aCoder encodeSize:_maxItemSize forKey:CPCollectionViewMaxItemSizeKey];
1565 
1566  [aCoder encodeInt:_maxNumberOfRows forKey:CPCollectionViewMaxNumberOfRowsKey];
1567  [aCoder encodeInt:_maxNumberOfColumns forKey:CPCollectionViewMaxNumberOfColumnsKey];
1568 
1569  [aCoder encodeBool:_isSelectable forKey:CPCollectionViewSelectableKey];
1570  [aCoder encodeBool:_allowsMultipleSelection forKey:CPCollectionViewAllowsMultipleSelectionKey];
1571 
1572  [aCoder encodeFloat:_verticalMargin forKey:CPCollectionViewVerticalMarginKey];
1573 
1574  [aCoder encodeObject:_backgroundColors forKey:CPCollectionViewBackgroundColorsKey];
1575 }
1576 
1577 @end
1578 
1580 
1584 - (BOOL)uniformSubviewsResizing
1585 {
1586  return _uniformSubviewsResizing;
1587 }
1588 
1592 - (void)setUniformSubviewsResizing:(BOOL)aValue
1593 {
1594  _uniformSubviewsResizing = aValue;
1595 }
1596 
1597 @end