API  0.9.6
 All Classes Files Functions Variables 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 
58 @implementation CPCollectionView : CPView
59 {
60  CPArray _content;
61  CPArray _items;
62 
63  CPData _itemData;
64  CPCollectionViewItem _itemPrototype;
65  CPCollectionViewItem _itemForDragging;
66  CPMutableArray _cachedItems;
67 
68  unsigned _maxNumberOfRows;
69  unsigned _maxNumberOfColumns;
70 
71  CGSize _minItemSize;
72  CGSize _maxItemSize;
73 
74  CPArray _backgroundColors;
75 
76  float _tileWidth;
77 
78  BOOL _isSelectable;
79  BOOL _allowsMultipleSelection;
80  BOOL _allowsEmptySelection;
81  CPIndexSet _selectionIndexes;
82 
83  CGSize _itemSize;
84 
85  float _horizontalMargin;
86  float _verticalMargin;
87 
88  unsigned _numberOfRows;
89  unsigned _numberOfColumns;
90 
91  id _delegate;
92 
93  CPEvent _mouseDownEvent;
94 }
95 
96 - (id)initWithFrame:(CGRect)aFrame
97 {
98  self = [super initWithFrame:aFrame];
99 
100  if (self)
101  {
102  _items = [];
103  _content = [];
104 
105  _cachedItems = [];
106 
107  _itemSize = CGSizeMakeZero();
108  _minItemSize = CGSizeMakeZero();
109  _maxItemSize = CGSizeMakeZero();
110 
111  [self setBackgroundColors:nil];
112 
113  _verticalMargin = 5.0;
114  _tileWidth = -1.0;
115 
116  _selectionIndexes = [CPIndexSet indexSet];
117  _allowsEmptySelection = YES;
118  _isSelectable = YES;
119  }
120 
121  return self;
122 }
123 
171 - (void)setItemPrototype:(CPCollectionViewItem)anItem
172 {
173  _cachedItems = [];
174  _itemData = nil;
175  _itemForDragging = nil;
176  _itemPrototype = anItem;
177 
178  [self reloadContent];
179 }
180 
184 - (CPCollectionViewItem)itemPrototype
185 {
186  return _itemPrototype;
187 }
188 
193 - (CPCollectionViewItem)newItemForRepresentedObject:(id)anObject
194 {
195  var item = nil;
196 
197  if (_cachedItems.length)
198  item = _cachedItems.pop();
199 
200  else
201  {
202  if (!_itemData)
203  if (_itemPrototype)
204  _itemData = [CPKeyedArchiver archivedDataWithRootObject:_itemPrototype];
205 
206  item = [CPKeyedUnarchiver unarchiveObjectWithData:_itemData];
207  }
208 
209  [item setRepresentedObject:anObject];
210  [[item view] setFrameSize:_itemSize];
211 
212  return item;
213 }
214 
215 // Working with the Responder Chain
219 - (BOOL)acceptsFirstResponder
220 {
221  return YES;
222 }
223 
227 - (BOOL)isFirstResponder
228 {
229  return [[self window] firstResponder] === self;
230 }
231 
232 // Setting the Content
244 - (void)setContent:(CPArray)anArray
245 {
246  _content = anArray;
247 
248  [self reloadContent];
249 }
250 
254 - (CPArray)content
255 {
256  return _content;
257 }
258 
262 - (CPArray)items
263 {
264  return _items;
265 }
266 
267 // Setting the Selection Mode
272 - (void)setSelectable:(BOOL)isSelectable
273 {
274  if (_isSelectable == isSelectable)
275  return;
276 
277  _isSelectable = isSelectable;
278 
279  if (!_isSelectable)
280  {
281  var index = CPNotFound,
282  itemCount = [_items count];
283 
284  // Be wary of invalid selection ranges since setContent: does not clear selection indexes.
285  while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound && index < itemCount)
286  [_items[index] setSelected:NO];
287  }
288 }
289 
294 - (BOOL)isSelectable
295 {
296  return _isSelectable;
297 }
298 
303 - (void)setAllowsEmptySelection:(BOOL)shouldAllowEmptySelection
304 {
305  _allowsEmptySelection = shouldAllowEmptySelection;
306 }
307 
311 - (BOOL)allowsEmptySelection
312 {
313  return _allowsEmptySelection;
314 }
315 
320 - (void)setAllowsMultipleSelection:(BOOL)shouldAllowMultipleSelection
321 {
322  _allowsMultipleSelection = shouldAllowMultipleSelection;
323 }
324 
328 - (BOOL)allowsMultipleSelection
329 {
330  return _allowsMultipleSelection;
331 }
332 
337 - (void)setSelectionIndexes:(CPIndexSet)anIndexSet
338 {
339  if (!anIndexSet)
340  anIndexSet = [CPIndexSet indexSet];
341  if (!_isSelectable || [_selectionIndexes isEqual:anIndexSet])
342  return;
343 
344  var index = CPNotFound,
345  itemCount = [_items count];
346 
347  // Be wary of invalid selection ranges since setContent: does not clear selection indexes.
348  while ((index = [_selectionIndexes indexGreaterThanIndex:index]) !== CPNotFound && index < itemCount)
349  [_items[index] setSelected:NO];
350 
351  _selectionIndexes = anIndexSet;
352 
353  var index = CPNotFound;
354 
355  while ((index = [_selectionIndexes indexGreaterThanIndex:index]) !== CPNotFound)
356  [_items[index] setSelected:YES];
357 
358  var binderClass = [[self class] _binderClassForBinding:@"selectionIndexes"];
359  [[binderClass getBinding:@"selectionIndexes" forObject:self] reverseSetValueFor:@"selectionIndexes"];
360 
361  if ([_delegate respondsToSelector:@selector(collectionViewDidChangeSelection:)])
362  [_delegate collectionViewDidChangeSelection:self];
363 }
364 
368 - (CPIndexSet)selectionIndexes
369 {
370  return [_selectionIndexes copy];
371 }
372 
373 /* @ignore */
374 - (void)reloadContent
375 {
376  // Remove current views
377  var count = _items.length;
378 
379  while (count--)
380  {
381  [[_items[count] view] removeFromSuperview];
382  [_items[count] setSelected:NO];
383 
384  _cachedItems.push(_items[count]);
385  }
386 
387  _items = [];
388 
389  if (!_itemPrototype)
390  return;
391 
392  var index = 0;
393 
394  count = _content.length;
395 
396  for (; index < count; ++index)
397  {
398  _items.push([self newItemForRepresentedObject:_content[index]]);
399 
400  [self addSubview:[_items[index] view]];
401  }
402 
403  index = CPNotFound;
404  // Be wary of invalid selection ranges since setContent: does not clear selection indexes.
405  while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound && index < count)
406  [_items[index] setSelected:YES];
407 
408  [self tile];
409 }
410 
411 /* @ignore */
412 - (void)tile
413 {
414  var width = CGRectGetWidth([self bounds]);
415 
416  if (width == _tileWidth)
417  return;
418 
419  // We try to fit as many views per row as possible. Any remaining space is then
420  // either proportioned out to the views (if their minSize != maxSize) or used as
421  // margin
422  var itemSize = CGSizeMakeCopy(_minItemSize);
423 
424  _numberOfColumns = MAX(1.0, FLOOR(width / itemSize.width));
425 
426  if (_maxNumberOfColumns > 0)
427  _numberOfColumns = MIN(_maxNumberOfColumns, _numberOfColumns);
428 
429  var remaining = width - _numberOfColumns * itemSize.width,
430  itemsNeedSizeUpdate = NO;
431 
432  if (remaining > 0 && itemSize.width < _maxItemSize.width)
433  itemSize.width = MIN(_maxItemSize.width, itemSize.width + FLOOR(remaining / _numberOfColumns));
434 
435  // When we ONE column and a non-integral width, the FLOORing above can cause the item width to be smaller than the total width.
436  if (_maxNumberOfColumns == 1 && itemSize.width < _maxItemSize.width && itemSize.width < width)
437  itemSize.width = MIN(_maxItemSize.width, width);
438 
439  if (!CGSizeEqualToSize(_itemSize, itemSize))
440  {
441  _itemSize = itemSize;
442  itemsNeedSizeUpdate = YES;
443  }
444 
445  var index = 0,
446  count = _items.length;
447 
448  if (_maxNumberOfColumns > 0 && _maxNumberOfRows > 0)
449  count = MIN(count, _maxNumberOfColumns * _maxNumberOfRows);
450 
451  _numberOfRows = CEIL(count / _numberOfColumns);
452 
453  _horizontalMargin = FLOOR((width - _numberOfColumns * itemSize.width) / (_numberOfColumns + 1));
454 
455  var x = _horizontalMargin,
456  y = -itemSize.height;
457 
458  for (; index < count; ++index)
459  {
460  if (index % _numberOfColumns == 0)
461  {
462  x = _horizontalMargin;
463  y += _verticalMargin + itemSize.height;
464  }
465 
466  var view = [_items[index] view];
467 
468  [view setFrameOrigin:CGPointMake(x, y)];
469 
470  if (itemsNeedSizeUpdate)
471  [view setFrameSize:_itemSize];
472 
473  x += itemSize.width + _horizontalMargin;
474  }
475 
476  var superview = [self superview],
477  proposedHeight = y + itemSize.height + _verticalMargin;
478 
479  if ([superview isKindOfClass:[CPClipView class]])
480  {
481  var superviewSize = [superview bounds].size;
482  proposedHeight = MAX(superviewSize.height, proposedHeight);
483  }
484 
485  _tileWidth = width;
486  [self setFrameSize:CGSizeMake(width, proposedHeight)];
487  _tileWidth = -1.0;
488 }
489 
490 - (void)resizeSubviewsWithOldSize:(CGSize)aSize
491 {
492  [self tile];
493 }
494 
495 // Laying Out the Collection View
500 - (void)setMaxNumberOfRows:(unsigned)aMaxNumberOfRows
501 {
502  if (_maxNumberOfRows == aMaxNumberOfRows)
503  return;
504 
505  _maxNumberOfRows = aMaxNumberOfRows;
506 
507  [self tile];
508 }
509 
513 - (unsigned)maxNumberOfRows
514 {
515  return _maxNumberOfRows;
516 }
517 
522 - (void)setMaxNumberOfColumns:(unsigned)aMaxNumberOfColumns
523 {
524  if (_maxNumberOfColumns == aMaxNumberOfColumns)
525  return;
526 
527  _maxNumberOfColumns = aMaxNumberOfColumns;
528 
529  [self tile];
530 }
531 
535 - (unsigned)maxNumberOfColumns
536 {
537  return _maxNumberOfColumns;
538 }
539 
543 - (unsigned)numberOfRows
544 {
545  return _numberOfRows;
546 }
547 
552 - (unsigned)numberOfColumns
553 {
554  return _numberOfColumns;
555 }
556 
561 - (void)setMinItemSize:(CGSize)aSize
562 {
563  if (aSize === nil || aSize === undefined)
564  [CPException raise:CPInvalidArgumentException reason:"Invalid value provided for minimum size"];
565  if (CGSizeEqualToSize(_minItemSize, aSize))
566  return;
567 
568  _minItemSize = CGSizeMakeCopy(aSize);
569 
570  [self tile];
571 }
572 
576 - (CGSize)minItemSize
577 {
578  return _minItemSize;
579 }
580 
585 - (void)setMaxItemSize:(CGSize)aSize
586 {
587  if (CGSizeEqualToSize(_maxItemSize, aSize))
588  return;
589 
590  _maxItemSize = CGSizeMakeCopy(aSize);
591 
592  [self tile];
593 }
594 
598 - (CGSize)maxItemSize
599 {
600  return _maxItemSize;
601 }
602 
603 - (void)setBackgroundColors:(CPArray)backgroundColors
604 {
605  if (_backgroundColors === backgroundColors)
606  return;
607 
608  _backgroundColors = backgroundColors;
609 
610  if (!_backgroundColors)
611  _backgroundColors = [CPColor whiteColor];
612 
613  if ([_backgroundColors count] === 1)
614  [self setBackgroundColor:_backgroundColors[0]];
615 
616  else
617  [self setBackgroundColor:nil];
618 
619  [self setNeedsDisplay:YES];
620 }
621 
622 - (CPArray)backgroundColors
623 {
624  return _backgroundColors;
625 }
626 
627 - (void)mouseUp:(CPEvent)anEvent
628 {
629  if ([_selectionIndexes count] && [anEvent clickCount] == 2 && [_delegate respondsToSelector:@selector(collectionView:didDoubleClickOnItemAtIndex:)])
630  [_delegate collectionView:self didDoubleClickOnItemAtIndex:[_selectionIndexes firstIndex]];
631 }
632 
633 - (void)mouseDown:(CPEvent)anEvent
634 {
635  _mouseDownEvent = anEvent;
636 
637  var location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
638  index = [self _indexAtPoint:location];
639 
640  if (index >= 0 && index < _items.length)
641  {
642  if (_allowsMultipleSelection && ([anEvent modifierFlags] & CPPlatformActionKeyMask || [anEvent modifierFlags] & CPShiftKeyMask))
643  {
644  if ([anEvent modifierFlags] & CPPlatformActionKeyMask)
645  {
646  var indexes = [_selectionIndexes copy];
647 
648  if ([indexes containsIndex:index])
649  [indexes removeIndex:index];
650  else
651  [indexes addIndex:index];
652  }
653  else if ([anEvent modifierFlags] & CPShiftKeyMask)
654  {
655  var firstSelectedIndex = [[self selectionIndexes] firstIndex],
656  newSelectedRange = nil;
657 
658  if (index < firstSelectedIndex)
659  newSelectedRange = CPMakeRange(index, (firstSelectedIndex - index) + 1);
660  else
661  newSelectedRange = CPMakeRange(firstSelectedIndex, (index - firstSelectedIndex) + 1);
662 
663  indexes = [[self selectionIndexes] copy];
664  [indexes addIndexesInRange:newSelectedRange];
665  }
666  }
667  else
668  indexes = [CPIndexSet indexSetWithIndex:index];
669 
670  [self setSelectionIndexes:indexes];
671 
672  // TODO Is it allowable for collection view items to become the first responder? In that case they
673  // may have become that at this point by virtue of CPWindow's sendEvent: mouse down handling, and
674  // the following line will rudely snatch it away from them. For most cases though, clicking on an
675  // item should naturally make the collection view the first responder so that keyboard navigation
676  // is enabled.
677  [[self window] makeFirstResponder:self];
678  }
679  else if (_allowsEmptySelection)
681 }
682 
683 - (void)mouseDragged:(CPEvent)anEvent
684 {
685  // Don't crash if we never registered the intial click.
686  if (!_mouseDownEvent)
687  return;
688 
689  var locationInWindow = [anEvent locationInWindow],
690  mouseDownLocationInWindow = [_mouseDownEvent locationInWindow];
691 
692  // FIXME: This is because Safari's drag hysteresis is 3px x 3px
693  if ((ABS(locationInWindow.x - mouseDownLocationInWindow.x) < 3) &&
694  (ABS(locationInWindow.y - mouseDownLocationInWindow.y) < 3))
695  return;
696 
697  if (![_delegate respondsToSelector:@selector(collectionView:dragTypesForItemsAtIndexes:)])
698  return;
699 
700  // If we don't have any selected items, we've clicked away, and thus the drag is meaningless.
701  if (![_selectionIndexes count])
702  return;
703 
704  if ([_delegate respondsToSelector:@selector(collectionView:canDragItemsAtIndexes:withEvent:)] &&
705  ![_delegate collectionView:self canDragItemsAtIndexes:_selectionIndexes withEvent:_mouseDownEvent])
706  return;
707 
708  // Set up the pasteboard
709  var dragTypes = [_delegate collectionView:self dragTypesForItemsAtIndexes:_selectionIndexes];
710 
711  [[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:dragTypes owner:self];
712 
713  if (!_itemForDragging)
714  _itemForDragging = [self newItemForRepresentedObject:_content[[_selectionIndexes firstIndex]]];
715  else
716  [_itemForDragging setRepresentedObject:_content[[_selectionIndexes firstIndex]]];
717 
718  var view = [_itemForDragging view];
719 
720  [view setFrameSize:_itemSize];
721  [view setAlphaValue:0.7];
722 
723  [self dragView:view
724  at:[[_items[[_selectionIndexes firstIndex]] view] frame].origin
725  offset:CGSizeMakeZero()
726  event:_mouseDownEvent
727  pasteboard:nil
728  source:self
729  slideBack:YES];
730 }
731 
737 - (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
738 {
739  [aPasteboard setData:[_delegate collectionView:self dataForItemsAtIndexes:_selectionIndexes forType:aType] forType:aType];
740 }
741 
742 // Cappuccino Additions
743 
749 - (void)setVerticalMargin:(float)aVerticalMargin
750 {
751  if (_verticalMargin == aVerticalMargin)
752  return;
753 
754  _verticalMargin = aVerticalMargin;
755 
756  [self tile];
757 }
758 
763 - (float)verticalMargin
764 {
765  return _verticalMargin;
766 }
767 
772 - (void)setDelegate:(id)aDelegate
773 {
774  _delegate = aDelegate;
775 }
776 
780 - (id)delegate
781 {
782  return _delegate;
783 }
784 
788 - (CPMenu)menuForEvent:(CPEvent)theEvent
789 {
790  if (![[self delegate] respondsToSelector:@selector(collectionView:menuForItemAtIndex:)])
791  return [super menuForEvent:theEvent];
792 
793  var location = [self convertPoint:[theEvent locationInWindow] fromView:nil],
794  index = [self _indexAtPoint:location];
795 
796  return [_delegate collectionView:self menuForItemAtIndex:index];
797 }
798 
799 - (int)_indexAtPoint:(CGPoint)thePoint
800 {
801  var row = FLOOR(thePoint.y / (_itemSize.height + _verticalMargin)),
802  column = FLOOR(thePoint.x / (_itemSize.width + _horizontalMargin));
803 
804  return row * _numberOfColumns + column;
805 }
806 
807 - (CPCollectionViewItem)itemAtIndex:(unsigned)anIndex
808 {
809  return [_items objectAtIndex:anIndex];
810 }
811 
812 - (CGRect)frameForItemAtIndex:(unsigned)anIndex
813 {
814  return [[[self itemAtIndex:anIndex] view] frame];
815 }
816 
817 - (CGRect)frameForItemsAtIndexes:(CPIndexSet)anIndexSet
818 {
819  var indexArray = [],
820  frame = CGRectNull;
821 
822  [anIndexSet getIndexes:indexArray maxCount:-1 inIndexRange:nil];
823 
824  var index = 0,
825  count = [indexArray count];
826 
827  for (; index < count; ++index)
828  frame = CGRectUnion(frame, [self frameForItemAtIndex:indexArray[index]]);
829 
830  return frame;
831 }
832 
833 @end
834 
836 
837 - (void)_modifySelectionWithNewIndex:(int)anIndex direction:(int)aDirection expand:(BOOL)shouldExpand
838 {
839  anIndex = MIN(MAX(anIndex, 0), [[self items] count] - 1);
840 
841  if (_allowsMultipleSelection && shouldExpand)
842  {
843  var indexes = [_selectionIndexes copy],
844  bottomAnchor = [indexes firstIndex],
845  topAnchor = [indexes lastIndex];
846 
847  // if the direction is backward (-1) check with the bottom anchor
848  if (aDirection === -1)
849  [indexes addIndexesInRange:CPMakeRange(anIndex, bottomAnchor - anIndex + 1)];
850  else
851  [indexes addIndexesInRange:CPMakeRange(topAnchor, anIndex - topAnchor + 1)];
852  }
853  else
854  indexes = [CPIndexSet indexSetWithIndex:anIndex];
855 
856  [self setSelectionIndexes:indexes];
857  [self _scrollToSelection];
858 }
859 
860 - (void)_scrollToSelection
861 {
862  var frame = [self frameForItemsAtIndexes:[self selectionIndexes]];
863 
864  if (!CGRectIsNull(frame))
865  [self scrollRectToVisible:frame];
866 }
867 
868 - (void)moveLeft:(id)sender
869 {
870  var index = [[self selectionIndexes] firstIndex];
871  if (index === CPNotFound)
872  index = [[self items] count];
873 
874  [self _modifySelectionWithNewIndex:index - 1 direction:-1 expand:NO];
875 }
876 
877 - (void)moveLeftAndModifySelection:(id)sender
878 {
879  var index = [[self selectionIndexes] firstIndex];
880  if (index === CPNotFound)
881  index = [[self items] count];
882 
883  [self _modifySelectionWithNewIndex:index - 1 direction:-1 expand:YES];
884 }
885 
886 - (void)moveRight:(id)sender
887 {
888  [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + 1 direction:1 expand:NO];
889 }
890 
891 - (void)moveRightAndModifySelection:(id)sender
892 {
893  [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + 1 direction:1 expand:YES];
894 }
895 
896 - (void)moveDown:(id)sender
897 {
898  [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + [self numberOfColumns] direction:1 expand:NO];
899 }
900 
901 - (void)moveDownAndModifySelection:(id)sender
902 {
903  [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + [self numberOfColumns] direction:1 expand:YES];
904 }
905 
906 - (void)moveUp:(id)sender
907 {
908  var index = [[self selectionIndexes] firstIndex];
909  if (index == CPNotFound)
910  index = [[self items] count];
911 
912  [self _modifySelectionWithNewIndex:index - [self numberOfColumns] direction:-1 expand:NO];
913 }
914 
915 - (void)moveUpAndModifySelection:(id)sender
916 {
917  var index = [[self selectionIndexes] firstIndex];
918  if (index == CPNotFound)
919  index = [[self items] count];
920 
921  [self _modifySelectionWithNewIndex:index - [self numberOfColumns] direction:-1 expand:YES];
922 }
923 
924 - (void)deleteBackward:(id)sender
925 {
926  if ([[self delegate] respondsToSelector:@selector(collectionView:shouldDeleteItemsAtIndexes:)])
927  {
928  [[self delegate] collectionView:self shouldDeleteItemsAtIndexes:[self selectionIndexes]];
929 
930  var index = [[self selectionIndexes] firstIndex];
931  if (index > [[self content] count] - 1)
933 
934  [self _scrollToSelection];
935  [self setNeedsDisplay:YES];
936  }
937 }
938 
939 - (void)keyDown:(CPEvent)anEvent
940 {
941  [self interpretKeyEvents:[anEvent]];
942 }
943 
944 @end
945 
947 
948 - (CGRect)rectForItemAtIndex:(int)anIndex
949 {
950  _CPReportLenientDeprecation([self class], _cmd, @selector(frameForItemAtIndex:));
951 
952  // Don't re-compute anything just grab the current frame
953  // This allows subclasses to override tile without messing this up.
954  return [self frameForItemAtIndex:anIndex];
955 }
956 
957 - (CGRect)rectForItemsAtIndexes:(CPIndexSet)anIndexSet
958 {
959  _CPReportLenientDeprecation([self class], _cmd, @selector(frameForItemsAtIndexes:));
960 
961  return [self frameForItemsAtIndexes:anIndexSet];
962 }
963 
964 @end
965 
966 var CPCollectionViewMinItemSizeKey = @"CPCollectionViewMinItemSizeKey",
967  CPCollectionViewMaxItemSizeKey = @"CPCollectionViewMaxItemSizeKey",
968  CPCollectionViewVerticalMarginKey = @"CPCollectionViewVerticalMarginKey",
969  CPCollectionViewMaxNumberOfRowsKey = @"CPCollectionViewMaxNumberOfRowsKey",
970  CPCollectionViewMaxNumberOfColumnsKey = @"CPCollectionViewMaxNumberOfColumnsKey",
971  CPCollectionViewSelectableKey = @"CPCollectionViewSelectableKey",
972  CPCollectionViewAllowsMultipleSelectionKey = @"CPCollectionViewAllowsMultipleSelectionKey",
973  CPCollectionViewBackgroundColorsKey = @"CPCollectionViewBackgroundColorsKey";
974 
975 
977 
978 - (void)awakeFromCib
979 {
980  [super awakeFromCib];
981 
982  var prototypeView = [_itemPrototype view];
983  if (prototypeView && (CGSizeEqualToSize(_minItemSize, CGSizeMakeZero()) || CGSizeEqualToSize(_maxItemSize, CGSizeMakeZero())))
984  {
985  var item = _itemPrototype;
986 
987  if (CGSizeEqualToSize(_minItemSize, CGSizeMakeZero()))
988  _minItemSize = [prototypeView frameSize];
989  else if (CGSizeEqualToSize(_maxItemSize, CGSizeMakeZero()))
990  _maxItemSize = [prototypeView frameSize];
991  }
992 }
993 
994 - (id)initWithCoder:(CPCoder)aCoder
995 {
996  self = [super initWithCoder:aCoder];
997 
998  if (self)
999  {
1000  _items = [];
1001  _content = [];
1002 
1003  _cachedItems = [];
1004 
1005  _itemSize = CGSizeMakeZero();
1006 
1007  _minItemSize = [aCoder decodeSizeForKey:CPCollectionViewMinItemSizeKey];
1008  _maxItemSize = [aCoder decodeSizeForKey:CPCollectionViewMaxItemSizeKey];
1009 
1010  _maxNumberOfRows = [aCoder decodeIntForKey:CPCollectionViewMaxNumberOfRowsKey];
1011  _maxNumberOfColumns = [aCoder decodeIntForKey:CPCollectionViewMaxNumberOfColumnsKey];
1012 
1013  _verticalMargin = [aCoder decodeFloatForKey:CPCollectionViewVerticalMarginKey];
1014 
1015  _isSelectable = [aCoder decodeBoolForKey:CPCollectionViewSelectableKey];
1016  _allowsMultipleSelection = [aCoder decodeBoolForKey:CPCollectionViewAllowsMultipleSelectionKey];
1017 
1018  [self setBackgroundColors:[aCoder decodeObjectForKey:CPCollectionViewBackgroundColorsKey]];
1019 
1020  _tileWidth = -1.0;
1021 
1022  _selectionIndexes = [CPIndexSet indexSet];
1023 
1024  _allowsEmptySelection = YES;
1025  }
1026 
1027  return self;
1028 }
1029 
1030 - (void)encodeWithCoder:(CPCoder)aCoder
1031 {
1032  [super encodeWithCoder:aCoder];
1033 
1034  if (!CGSizeEqualToSize(_minItemSize, CGSizeMakeZero()))
1035  [aCoder encodeSize:_minItemSize forKey:CPCollectionViewMinItemSizeKey];
1036 
1037  if (!CGSizeEqualToSize(_maxItemSize, CGSizeMakeZero()))
1038  [aCoder encodeSize:_maxItemSize forKey:CPCollectionViewMaxItemSizeKey];
1039 
1040  [aCoder encodeInt:_maxNumberOfRows forKey:CPCollectionViewMaxNumberOfRowsKey];
1041  [aCoder encodeInt:_maxNumberOfColumns forKey:CPCollectionViewMaxNumberOfColumnsKey];
1042 
1043  [aCoder encodeBool:_isSelectable forKey:CPCollectionViewSelectableKey];
1044  [aCoder encodeBool:_allowsMultipleSelection forKey:CPCollectionViewAllowsMultipleSelectionKey];
1045 
1046  [aCoder encodeFloat:_verticalMargin forKey:CPCollectionViewVerticalMarginKey];
1047 
1048  [aCoder encodeObject:_backgroundColors forKey:CPCollectionViewBackgroundColorsKey];
1049 }
1050 
1051 @end