API  0.9.7
 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 
59 
60 @implementation CPCollectionView : CPView
61 {
62  CPArray _content;
63  CPArray _items;
64 
65  CPData _itemData;
66  CPCollectionViewItem _itemPrototype;
67  CPCollectionViewItem _itemForDragging;
68  CPMutableArray _cachedItems;
69 
70  unsigned _maxNumberOfRows;
71  unsigned _maxNumberOfColumns;
72 
73  CGSize _minItemSize;
74  CGSize _maxItemSize;
75 
76  CPArray _backgroundColors;
77 
78  float _tileWidth;
79 
80  BOOL _isSelectable;
81  BOOL _allowsMultipleSelection;
82  BOOL _allowsEmptySelection;
83  CPIndexSet _selectionIndexes;
84 
85  CGSize _itemSize;
86 
87  float _horizontalMargin;
88  float _verticalMargin;
89 
90  unsigned _numberOfRows;
91  unsigned _numberOfColumns;
92 
93  id _delegate;
94 
95  CPEvent _mouseDownEvent;
96 
97  BOOL _needsMinMaxItemSizeUpdate;
98  CGSize _storedFrameSize;
99 
100  BOOL _uniformSubviewsResizing;
101  BOOL _lockResizing;
102 
103  CPInteger _currentDropIndex;
104  CPDragOperation _currentDragOperation;
105 
106  _CPCollectionViewDropIndicator _dropView;
107 }
108 
109 - (id)initWithFrame:(CGRect)aFrame
110 {
111  self = [super initWithFrame:aFrame];
112 
113  if (self)
114  {
115  _maxNumberOfRows = 0;
116  _maxNumberOfColumns = 0;
117 
118  _minItemSize = CGSizeMakeZero();
119  _maxItemSize = CGSizeMakeZero();
120 
121  [self setBackgroundColors:nil];
122 
123  _verticalMargin = 5.0;
124  _isSelectable = YES;
125  _allowsEmptySelection = YES;
126 
127  [self _init];
128  }
129 
130  return self;
131 }
132 
133 - (void)_init
134 {
135  _content = [];
136 
137  _items = [];
138  _cachedItems = [];
139 
140  _numberOfColumns = CPNotFound;
141  _numberOfRows = CPNotFound;
142 
143  _itemSize = CGSizeMakeZero();
144 
145  _selectionIndexes = [CPIndexSet indexSet];
146 
147  _storedFrameSize = CGSizeMakeZero();
148 
149  _needsMinMaxItemSizeUpdate = YES;
150  _uniformSubviewsResizing = NO;
151  _lockResizing = NO;
152 
153  _currentDropIndex = -1;
154  _currentDragOperation = CPDragOperationNone;
155  _dropView = nil;
156 
157  [self setAutoresizesSubviews:NO];
158  [self setAutoresizingMask:0];
159 }
160 
205 - (void)setItemPrototype:(CPCollectionViewItem)anItem
206 {
207  _cachedItems = [];
208  _itemData = nil;
209  _itemForDragging = nil;
210  _itemPrototype = anItem;
211 
212  [self _reloadContentCachingRemovedItems:NO];
213 }
214 
218 - (CPCollectionViewItem)itemPrototype
219 {
220  return _itemPrototype;
221 }
222 
227 - (CPCollectionViewItem)newItemForRepresentedObject:(id)anObject
228 {
229  var item = nil;
230 
231  if (_cachedItems.length)
232  item = _cachedItems.pop();
233 
234  else
235  item = [_itemPrototype copy];
236 
237  [item setRepresentedObject:anObject];
238  [[item view] setFrameSize:_itemSize];
239 
240  return item;
241 }
242 
243 // Working with the Responder Chain
247 - (BOOL)acceptsFirstResponder
248 {
249  return YES;
250 }
251 
255 - (BOOL)isFirstResponder
256 {
257  return [[self window] firstResponder] === self;
258 }
259 
260 // Setting the Content
272 - (void)setContent:(CPArray)anArray
273 {
274  _content = anArray;
275 
276  [self reloadContent];
277 }
278 
282 - (CPArray)content
283 {
284  return _content;
285 }
286 
290 - (CPArray)items
291 {
292  return _items;
293 }
294 
295 // Setting the Selection Mode
300 - (void)setSelectable:(BOOL)isSelectable
301 {
302  if (_isSelectable == isSelectable)
303  return;
304 
305  _isSelectable = isSelectable;
306 
307  if (!_isSelectable)
308  {
309  var index = CPNotFound,
310  itemCount = [_items count];
311 
312  // Be wary of invalid selection ranges since setContent: does not clear selection indexes.
313  while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound && index < itemCount)
314  [_items[index] setSelected:NO];
315  }
316 }
317 
322 - (BOOL)isSelectable
323 {
324  return _isSelectable;
325 }
326 
331 - (void)setAllowsEmptySelection:(BOOL)shouldAllowEmptySelection
332 {
333  _allowsEmptySelection = shouldAllowEmptySelection;
334 }
335 
339 - (BOOL)allowsEmptySelection
340 {
341  return _allowsEmptySelection;
342 }
343 
348 - (void)setAllowsMultipleSelection:(BOOL)shouldAllowMultipleSelection
349 {
350  _allowsMultipleSelection = shouldAllowMultipleSelection;
351 }
352 
356 - (BOOL)allowsMultipleSelection
357 {
358  return _allowsMultipleSelection;
359 }
360 
365 - (void)setSelectionIndexes:(CPIndexSet)anIndexSet
366 {
367  if (!anIndexSet)
368  anIndexSet = [CPIndexSet indexSet];
369  if (!_isSelectable || [_selectionIndexes isEqual:anIndexSet])
370  return;
371 
372  var index = CPNotFound,
373  itemCount = [_items count];
374 
375  // Be wary of invalid selection ranges since setContent: does not clear selection indexes.
376  while ((index = [_selectionIndexes indexGreaterThanIndex:index]) !== CPNotFound && index < itemCount)
377  [_items[index] setSelected:NO];
378 
379  _selectionIndexes = anIndexSet;
380 
381  var index = CPNotFound;
382 
383  while ((index = [_selectionIndexes indexGreaterThanIndex:index]) !== CPNotFound)
384  [_items[index] setSelected:YES];
385 
386  var binderClass = [[self class] _binderClassForBinding:@"selectionIndexes"];
387  [[binderClass getBinding:@"selectionIndexes" forObject:self] reverseSetValueFor:@"selectionIndexes"];
388 
389  if ([_delegate respondsToSelector:@selector(collectionViewDidChangeSelection:)])
390  {
391  CPLog.warn("The delegate method collectionViewDidChangeSelection: is deprecated and will be removed in a future version, please bind to selectionIndexes instead.");
392  [_delegate collectionViewDidChangeSelection:self];
393  }
394 }
395 
399 - (CPIndexSet)selectionIndexes
400 {
401  return [_selectionIndexes copy];
402 }
403 
404 - (void)reloadContent
405 {
406  [self _reloadContentCachingRemovedItems:YES];
407 }
408 
409 /* @ignore */
410 - (void)_reloadContentCachingRemovedItems:(BOOL)shouldCache
411 {
412  // Remove current views
413  var count = _items.length;
414 
415  while (count--)
416  {
417  [[_items[count] view] removeFromSuperview];
418  [_items[count] setSelected:NO];
419 
420  if (shouldCache)
421  _cachedItems.push(_items[count]);
422  }
423 
424  _items = [];
425 
426  if (!_itemPrototype)
427  return;
428 
429  var index = 0;
430 
431  count = _content.length;
432 
433  for (; index < count; ++index)
434  {
435  _items.push([self newItemForRepresentedObject:_content[index]]);
436 
437  [self addSubview:[_items[index] view]];
438  }
439 
440  index = CPNotFound;
441  // Be wary of invalid selection ranges since setContent: does not clear selection indexes.
442  while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound && index < count)
443  [_items[index] setSelected:YES];
444 
445  [self tileIfNeeded:NO];
446 }
447 
448 - (void)resizeSubviewsWithOldSize:(CGSize)oldBoundsSize
449 {
450  // Desactivate subviews autoresizing
451 }
452 
453 - (void)resizeWithOldSuperviewSize:(CGSize)oldBoundsSize
454 {
455  if (_lockResizing)
456  return;
457 
458  _lockResizing = YES;
459 
460  [self tile];
461 
462  _lockResizing = NO;
463 }
464 
465 - (void)tile
466 {
467  [self tileIfNeeded:!_uniformSubviewsResizing];
468 }
469 
470 - (void)tileIfNeeded:(BOOL)lazyFlag
471 {
472  var frameSize = [[self superview] frameSize],
473  count = _items.length,
474  oldNumberOfColumns = _numberOfColumns,
475  oldNumberOfRows = _numberOfRows,
476  oldItemSize = _itemSize,
477  storedFrameSize = _storedFrameSize;
478 
479  // No need to tile if we are not yet placed in the view hierarchy.
480  if (!frameSize)
481  return;
482 
483  [self _updateMinMaxItemSizeIfNeeded];
484 
485  [self _computeGridWithSize:frameSize count:@ref(count)];
486 
487  //CPLog.debug("frameSize="+CPStringFromSize(frameSize) + "itemSize="+CPStringFromSize(itemSize) + " ncols=" + colsRowsCount[0] +" nrows="+ colsRowsCount[1]+" displayCount="+ colsRowsCount[2]);
488 
489  [self setFrameSize:_storedFrameSize];
490 
491  //CPLog.debug("OLD " + oldNumberOfColumns + " NEW " + _numberOfColumns);
492  if (!lazyFlag ||
493  _numberOfColumns !== oldNumberOfColumns ||
494  _numberOfRows !== oldNumberOfRows ||
495  !CGSizeEqualToSize(_itemSize, oldItemSize))
496 
497  [self displayItems:_items frameSize:_storedFrameSize itemSize:_itemSize columns:_numberOfColumns rows:_numberOfRows count:count];
498 }
499 
500 - (void)_computeGridWithSize:(CGSize)aSuperviewSize count:(Function)countRef
501 {
502  var width = aSuperviewSize.width,
503  height = aSuperviewSize.height,
504  itemSize = CGSizeMakeCopy(_minItemSize),
505  maxItemSizeWidth = _maxItemSize.width,
506  maxItemSizeHeight = _maxItemSize.height,
507  itemsCount = [_items count],
508  numberOfRows,
509  numberOfColumns;
510 
511  numberOfColumns = FLOOR(width / itemSize.width);
512 
513  if (maxItemSizeWidth == 0)
514  numberOfColumns = MIN(numberOfColumns, _maxNumberOfColumns);
515 
516  if (_maxNumberOfColumns > 0)
517  numberOfColumns = MIN(MIN(_maxNumberOfColumns, itemsCount), numberOfColumns);
518 
519  numberOfColumns = MAX(1.0, numberOfColumns);
520 
521  itemSize.width = FLOOR(width / numberOfColumns);
522 
523  if (maxItemSizeWidth > 0)
524  {
525  itemSize.width = MIN(maxItemSizeWidth, itemSize.width);
526 
527  if (numberOfColumns == 1)
528  itemSize.width = MIN(maxItemSizeWidth, width);
529  }
530 
531  numberOfRows = CEIL(itemsCount / numberOfColumns);
532 
533  if (_maxNumberOfRows > 0)
534  numberOfRows = MIN(numberOfRows, _maxNumberOfRows);
535 
536  height = MAX(height, numberOfRows * (_minItemSize.height + _verticalMargin));
537 
538  var itemSizeHeight = FLOOR(height / numberOfRows);
539 
540  if (maxItemSizeHeight > 0)
541  itemSizeHeight = MIN(itemSizeHeight, maxItemSizeHeight);
542 
543  _itemSize = CGSizeMake(MAX(_minItemSize.width, itemSize.width), MAX(_minItemSize.height, itemSizeHeight));
544  _storedFrameSize = CGSizeMake(MAX(width, _minItemSize.width), height);
545  _numberOfColumns = numberOfColumns;
546  _numberOfRows = numberOfRows;
547  countRef(MIN(itemsCount, numberOfColumns * numberOfRows));
548 }
549 
550 - (void)displayItems:(CPArray)displayItems frameSize:(CGSize)aFrameSize itemSize:(CGSize)anItemSize columns:(CPInteger)numberOfColumns rows:(CPInteger)numberOfRows count:(CPInteger)displayCount
551 {
552 // CPLog.debug("DISPLAY ITEMS " + numberOfColumns + " " + numberOfRows);
553 
554  _horizontalMargin = _uniformSubviewsResizing ? FLOOR((aFrameSize.width - numberOfColumns * anItemSize.width) / (numberOfColumns + 1)) : HORIZONTAL_MARGIN;
555 
556  var x = _horizontalMargin,
557  y = -anItemSize.height;
558 
559  [displayItems enumerateObjectsUsingBlock:function(item, idx, stop)
560  {
561  var view = [item view];
562 
563  if (idx >= displayCount)
564  {
565  [view setFrameOrigin:CGPointMake(-anItemSize.width, -anItemSize.height)];
566  return;
567  }
568 
569  if (idx % numberOfColumns == 0)
570  {
571  x = _horizontalMargin;
572  y += _verticalMargin + anItemSize.height;
573  }
574 
575  [view setFrameOrigin:CGPointMake(x, y)];
576  [view setFrameSize:anItemSize];
577 
578  x += anItemSize.width + _horizontalMargin;
579  }];
580 }
581 
582 - (void)_updateMinMaxItemSizeIfNeeded
583 {
584  if (!_needsMinMaxItemSizeUpdate)
585  return;
586 
587  var prototypeView;
588 
589  if (_itemPrototype && (prototypeView = [_itemPrototype view]))
590  {
591  if (_minItemSize.width == 0)
592  _minItemSize.width = [prototypeView frameSize].width;
593 
594  if (_minItemSize.height == 0)
595  _minItemSize.height = [prototypeView frameSize].height;
596 
597  if (_maxItemSize.height == 0 && !([prototypeView autoresizingMask] & CPViewHeightSizable))
598  _maxItemSize.height = [prototypeView frameSize].height;
599 
600  if (_maxItemSize.width == 0 && !([prototypeView autoresizingMask] & CPViewWidthSizable))
601  _maxItemSize.width = [prototypeView frameSize].width;
602 
603  _needsMinMaxItemSizeUpdate = NO;
604  }
605 }
606 
607 // Laying Out the Collection View
612 - (void)setMaxNumberOfRows:(unsigned)aMaxNumberOfRows
613 {
614  if (_maxNumberOfRows == aMaxNumberOfRows)
615  return;
616 
617  _maxNumberOfRows = aMaxNumberOfRows;
618 
619  [self tile];
620 }
621 
625 - (unsigned)maxNumberOfRows
626 {
627  return _maxNumberOfRows;
628 }
629 
634 - (void)setMaxNumberOfColumns:(unsigned)aMaxNumberOfColumns
635 {
636  if (_maxNumberOfColumns == aMaxNumberOfColumns)
637  return;
638 
639  _maxNumberOfColumns = aMaxNumberOfColumns;
640 
641  [self tile];
642 }
643 
647 - (unsigned)maxNumberOfColumns
648 {
649  return _maxNumberOfColumns;
650 }
651 
655 - (unsigned)numberOfRows
656 {
657  return _numberOfRows;
658 }
659 
664 - (unsigned)numberOfColumns
665 {
666  return _numberOfColumns;
667 }
668 
673 - (void)setMinItemSize:(CGSize)aSize
674 {
675  if (aSize === nil || aSize === undefined)
676  [CPException raise:CPInvalidArgumentException reason:"Invalid value provided for minimum size"];
677 
678  if (CGSizeEqualToSize(_minItemSize, aSize))
679  return;
680 
681  _minItemSize = CGSizeMakeCopy(aSize);
682 
683  if (CGSizeEqualToSize(_minItemSize, CGSizeMakeZero()))
684  _needsMinMaxItemSizeUpdate = YES;
685 
686  [self tile];
687 }
688 
692 - (CGSize)minItemSize
693 {
694  return _minItemSize;
695 }
696 
701 - (void)setMaxItemSize:(CGSize)aSize
702 {
703  if (CGSizeEqualToSize(_maxItemSize, aSize))
704  return;
705 
706  _maxItemSize = CGSizeMakeCopy(aSize);
707 
708 // if (_maxItemSize.width == 0 || _maxItemSize.height == 0)
709 // _needsMinMaxItemSizeUpdate = YES;
710 
711  [self tile];
712 }
713 
717 - (CGSize)maxItemSize
718 {
719  return _maxItemSize;
720 }
721 
722 - (void)setBackgroundColors:(CPArray)backgroundColors
723 {
724  if (_backgroundColors === backgroundColors)
725  return;
726 
727  _backgroundColors = backgroundColors;
728 
729  if (!_backgroundColors)
730  _backgroundColors = [[CPColor whiteColor]];
731 
732  if ([_backgroundColors count] === 1)
733  [self setBackgroundColor:_backgroundColors[0]];
734 
735  else
736  [self setBackgroundColor:nil];
737 
738  [self setNeedsDisplay:YES];
739 }
740 
741 - (CPArray)backgroundColors
742 {
743  return _backgroundColors;
744 }
745 
746 - (void)mouseUp:(CPEvent)anEvent
747 {
748  if ([_selectionIndexes count] && [anEvent clickCount] == 2 && [_delegate respondsToSelector:@selector(collectionView:didDoubleClickOnItemAtIndex:)])
749  [_delegate collectionView:self didDoubleClickOnItemAtIndex:[_selectionIndexes firstIndex]];
750 }
751 
752 - (void)mouseDown:(CPEvent)anEvent
753 {
754  _mouseDownEvent = anEvent;
755 
756  var location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
757  index = [self _indexAtPoint:location];
758 
759  if (index >= 0 && index < _items.length)
760  {
761  if (_allowsMultipleSelection && ([anEvent modifierFlags] & CPPlatformActionKeyMask || [anEvent modifierFlags] & CPShiftKeyMask))
762  {
763  if ([anEvent modifierFlags] & CPPlatformActionKeyMask)
764  {
765  var indexes = [_selectionIndexes copy];
766 
767  if ([indexes containsIndex:index])
768  [indexes removeIndex:index];
769  else
770  [indexes addIndex:index];
771  }
772  else if ([anEvent modifierFlags] & CPShiftKeyMask)
773  {
774  var firstSelectedIndex = [[self selectionIndexes] firstIndex],
775  newSelectedRange = nil;
776 
777  // This catches the case where the shift key is held down for the first selection.
778  if (firstSelectedIndex === CPNotFound)
779  firstSelectedIndex = index;
780 
781  if (index < firstSelectedIndex)
782  newSelectedRange = CPMakeRange(index, (firstSelectedIndex - index) + 1);
783  else
784  newSelectedRange = CPMakeRange(firstSelectedIndex, (index - firstSelectedIndex) + 1);
785 
786  indexes = [[self selectionIndexes] copy];
787  [indexes addIndexesInRange:newSelectedRange];
788  }
789  }
790  else
791  indexes = [CPIndexSet indexSetWithIndex:index];
792 
793  [self setSelectionIndexes:indexes];
794 
795  // TODO Is it allowable for collection view items to become the first responder? In that case they
796  // may have become that at this point by virtue of CPWindow's sendEvent: mouse down handling, and
797  // the following line will rudely snatch it away from them. For most cases though, clicking on an
798  // item should naturally make the collection view the first responder so that keyboard navigation
799  // is enabled.
800  [[self window] makeFirstResponder:self];
801  }
802  else if (_allowsEmptySelection)
804 }
805 
806 // Cappuccino Additions
807 
813 - (void)setVerticalMargin:(float)aVerticalMargin
814 {
815  if (_verticalMargin == aVerticalMargin)
816  return;
817 
818  _verticalMargin = aVerticalMargin;
819 
820  [self tile];
821 }
822 
823 - (void)setUniformSubviewsResizing:(BOOL)flag
824 {
825  _uniformSubviewsResizing = flag;
826  [self tileIfNeeded:NO];
827 }
828 
829 
834 - (float)verticalMargin
835 {
836  return _verticalMargin;
837 }
838 
843 - (void)setDelegate:(id)aDelegate
844 {
845  _delegate = aDelegate;
846 }
847 
851 - (id)delegate
852 {
853  return _delegate;
854 }
855 
859 - (CPMenu)menuForEvent:(CPEvent)theEvent
860 {
861  if (![[self delegate] respondsToSelector:@selector(collectionView:menuForItemAtIndex:)])
862  return [super menuForEvent:theEvent];
863 
864  var location = [self convertPoint:[theEvent locationInWindow] fromView:nil],
865  index = [self _indexAtPoint:location];
866 
867  return [_delegate collectionView:self menuForItemAtIndex:index];
868 }
869 
870 - (int)_indexAtPoint:(CGPoint)thePoint
871 {
872  var column = FLOOR(thePoint.x / (_itemSize.width + _horizontalMargin));
873 
874  if (column < _numberOfColumns)
875  {
876  var row = FLOOR(thePoint.y / (_itemSize.height + _verticalMargin));
877 
878  if (row < _numberOfRows)
879  return (row * _numberOfColumns + column);
880  }
881 
882  return CPNotFound;
883 }
884 
885 - (CPCollectionViewItem)itemAtIndex:(CPUInteger)anIndex
886 {
887  return [_items objectAtIndex:anIndex];
888 }
889 
890 - (CGRect)frameForItemAtIndex:(CPUInteger)anIndex
891 {
892  return [[[self itemAtIndex:anIndex] view] frame];
893 }
894 
895 - (CGRect)frameForItemsAtIndexes:(CPIndexSet)anIndexSet
896 {
897  var indexArray = [],
898  frame = CGRectNull;
899 
900  [anIndexSet getIndexes:indexArray maxCount:-1 inIndexRange:nil];
901 
902  var index = 0,
903  count = [indexArray count];
904 
905  for (; index < count; ++index)
906  frame = CGRectUnion(frame, [self frameForItemAtIndex:indexArray[index]]);
907 
908  return frame;
909 }
910 
911 @end
912 
914 /*
915  TODO: dropOperation is not supported yet. The visible drop operation is like CPCollectionViewDropBefore.
916 */
917 
923 - (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
924 {
925  [aPasteboard setData:[_delegate collectionView:self dataForItemsAtIndexes:_selectionIndexes forType:aType] forType:aType];
926 }
927 
928 - (void)_createDropIndicatorIfNeeded
929 {
930  // Create and position the drop indicator view.
931 
932  if (!_dropView)
933  _dropView = [[_CPCollectionViewDropIndicator alloc] initWithFrame:CGRectMake(-8, -8, 0, 0)];
934 
935  [_dropView setFrameSize:CGSizeMake(10, _itemSize.height + _verticalMargin)];
936  [self addSubview:_dropView];
937 }
938 
939 - (void)mouseDragged:(CPEvent)anEvent
940 {
941  // Don't crash if we never registered the intial click.
942  if (!_mouseDownEvent)
943  return;
944 
945  [self _createDropIndicatorIfNeeded];
946 
947  var locationInWindow = [anEvent locationInWindow],
948  mouseDownLocationInWindow = [_mouseDownEvent locationInWindow];
949 
950  // FIXME: This is because Safari's drag hysteresis is 3px x 3px
951  if ((ABS(locationInWindow.x - mouseDownLocationInWindow.x) < 3) &&
952  (ABS(locationInWindow.y - mouseDownLocationInWindow.y) < 3))
953  return;
954 
955  if (![_delegate respondsToSelector:@selector(collectionView:dragTypesForItemsAtIndexes:)])
956  return;
957 
958  // If we don't have any selected items, we've clicked away, and thus the drag is meaningless.
959  if (![_selectionIndexes count])
960  return;
961 
962  if ([_delegate respondsToSelector:@selector(collectionView:canDragItemsAtIndexes:withEvent:)] &&
963  ![_delegate collectionView:self canDragItemsAtIndexes:_selectionIndexes withEvent:_mouseDownEvent])
964  return;
965 
966  // Set up the pasteboard
967  var dragTypes = [_delegate collectionView:self dragTypesForItemsAtIndexes:_selectionIndexes];
968 
969  [[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:dragTypes owner:self];
970 
971  var dragImageOffset = CGSizeMakeZero(),
972  view = [self _draggingViewForItemsAtIndexes:_selectionIndexes withEvent:_mouseDownEvent offset:dragImageOffset];
973 
974  [view setFrameSize:_itemSize];
975  [view setAlphaValue:0.7];
976 
977  var dragLocation = [self convertPoint:locationInWindow fromView:nil],
978  dragPoint = CGPointMake(dragLocation.x - _itemSize.width / 2 , dragLocation.y - _itemSize.height / 2);
979 
980  [self dragView:view
981  at:dragPoint
982  offset:dragImageOffset
983  event:_mouseDownEvent
984  pasteboard:nil
985  source:self
986  slideBack:YES];
987 }
988 
989 - (CPView)_draggingViewForItemsAtIndexes:(CPIndexSet)indexes withEvent:(CPEvent)anEvent offset:(CGPoint)offset
990 {
991  if ([_delegate respondsToSelector:@selector(collectionView:draggingViewForItemsAtIndexes:withEvent:offset:)])
992  return [_delegate collectionView:self draggingViewForItemsAtIndexes:indexes withEvent:anEvent offset:offset];
993 
994  return [self draggingViewForItemsAtIndexes:indexes withEvent:anEvent offset:offset];
995 }
996 
997 - (CPView)draggingViewForItemsAtIndexes:(CPIndexSet)indexes withEvent:(CPEvent)event offset:(CGPoint)dragImageOffset
998 {
999  var idx = _content[[indexes firstIndex]];
1000 
1001  if (!_itemForDragging)
1002  _itemForDragging = [self newItemForRepresentedObject:idx];
1003  else
1004  [_itemForDragging setRepresentedObject:idx];
1005 
1006  return [_itemForDragging view];
1007 }
1008 
1009 - (BOOL)_canDragItemsAtIndexes:(CPIndexSet)indexes withEvent:(CPEvent)anEvent
1010 {
1011  if ([self respondsToSelector:@selector(collectionView:canDragItemsAtIndexes:withEvent:)])
1012  return [_delegate collectionView:self canDragItemsAtIndexes:indexes withEvent:anEvent];
1013 
1014  return YES;
1015 }
1016 
1017 - (CPDragOperation)draggingEntered:(id)draggingInfo
1018 {
1019  var dropIndex = -1,
1020  dropIndexRef = @ref(dropIndex),
1021  dragOp = [self _validateDragWithInfo:draggingInfo dropIndex:dropIndexRef dropOperation:1];
1022 
1023  dropIndex = dropIndexRef();
1024 
1025  [self _createDropIndicatorIfNeeded];
1026 
1027  [self _updateDragAndDropStateWithDraggingInfo:draggingInfo newDragOperation:dragOp newDropIndex:dropIndex newDropOperation:1];
1028 
1029  return _currentDragOperation;
1030 }
1031 
1032 - (CPDragOperation)draggingUpdated:(id)draggingInfo
1033 {
1034  if (![self _dropIndexDidChange:draggingInfo])
1035  return _currentDragOperation;
1036 
1037  var dropIndex,
1038  dropIndexRef = @ref(dropIndex);
1039 
1040  var dragOperation = [self _validateDragWithInfo:draggingInfo dropIndex:dropIndexRef dropOperation:1];
1041 
1042  dropIndex = dropIndexRef();
1043 
1044  [self _updateDragAndDropStateWithDraggingInfo:draggingInfo newDragOperation:dragOperation newDropIndex:dropIndex newDropOperation:1];
1045 
1046  return dragOperation;
1047 }
1048 
1049 - (CPDragOperation)_validateDragWithInfo:(id)draggingInfo dropIndex:(Function)dropIndexRef dropOperation:(int)dropOperation
1050 {
1051  var result = CPDragOperationMove,
1052  dropIndex = [self _dropIndexForDraggingInfo:draggingInfo proposedDropOperation:dropOperation];
1053 
1054  if ([_delegate respondsToSelector:@selector(collectionView:validateDrop:proposedIndex:dropOperation:)])
1055  {
1056  var dropIndexRef2 = @ref(dropIndex);
1057 
1058  result = [_delegate collectionView:self validateDrop:draggingInfo proposedIndex:dropIndexRef2 dropOperation:dropOperation];
1059 
1060  if (result !== CPDragOperationNone)
1061  {
1062  dropIndex = dropIndexRef2();
1063  }
1064  }
1065 
1066  dropIndexRef(dropIndex);
1067 
1068  return result;
1069 }
1070 
1071 - (void)draggingExited:(id)draggingInfo
1072 {
1073  [self _updateDragAndDropStateWithDraggingInfo:draggingInfo newDragOperation:0 newDropIndex:-1 newDropOperation:1];
1074 }
1075 
1076 - (void)draggingEnded:(id)draggingInfo
1077 {
1078  [self _updateDragAndDropStateWithDraggingInfo:draggingInfo newDragOperation:0 newDropIndex:-1 newDropOperation:1];
1079 }
1080 
1081 /*
1082 Not supported. Use -collectionView:dataForItemsAtIndexes:fortype:
1083 - (BOOL)_writeItemsAtIndexes:(CPIndexSet)indexes toPasteboard:(CPPasteboard)pboard
1084 {
1085  if ([self respondsToSelector:@selector(collectionView:writeItemsAtIndexes:toPasteboard:)])
1086  return [_delegate collectionView:self writeItemsAtIndexes:indexes toPasteboard:pboard];
1087 
1088  return NO;
1089 }
1090 */
1091 
1092 - (BOOL)performDragOperation:(id)draggingInfo
1093 {
1094  var result = NO;
1095 
1096  if (_currentDragOperation && _currentDropIndex !== -1)
1097  result = [_delegate collectionView:self acceptDrop:draggingInfo index:_currentDropIndex dropOperation:1];
1098 
1099  [self draggingEnded:draggingInfo]; // Is this correct ?
1100 
1101  return result;
1102 }
1103 
1104 - (void)_updateDragAndDropStateWithDraggingInfo:(id)draggingInfo newDragOperation:(CPDragOperation)dragOperation newDropIndex:(CPInteger)dropIndex newDropOperation:(CPInteger)dropOperation
1105 {
1106  _currentDropIndex = dropIndex;
1107  _currentDragOperation = dragOperation;
1108 
1109  var frameOrigin,
1110  dropviewFrameWidth = CGRectGetWidth([_dropView frame]);
1111 
1112  if (_currentDropIndex == -1 || _currentDragOperation == CPDragOperationNone)
1113  frameOrigin = CGPointMake(-dropviewFrameWidth, 0);
1114  else if (_currentDropIndex == 0)
1115  frameOrigin = CGPointMake(0, 0);
1116  else
1117  {
1118  var offset;
1119 
1120  if ((_currentDropIndex % _numberOfColumns) !== 0 || _currentDropIndex == [_items count])
1121  {
1122  dropIndex = _currentDropIndex - 1;
1123  offset = (_horizontalMargin - dropviewFrameWidth) / 2;
1124  }
1125  else
1126  {
1127  offset = - _itemSize.width - dropviewFrameWidth - (_horizontalMargin - dropviewFrameWidth) / 2;
1128  }
1129 
1130  var rect = [self frameForItemAtIndex:dropIndex];
1131 
1132  frameOrigin = CGPointMake(CGRectGetMaxX(rect) + offset, rect.origin.y - _verticalMargin);
1133  }
1134 
1135  [_dropView setFrameOrigin:frameOrigin];
1136 }
1137 
1138 - (BOOL)_dropIndexDidChange:(id)draggingInfo
1139 {
1140  var dropIndex = [self _dropIndexForDraggingInfo:draggingInfo proposedDropOperation:1];
1141 
1142  if (dropIndex == CPNotFound)
1143  dropIndex = [[self content] count];
1144 
1145  return (_currentDropIndex !== dropIndex)
1146 }
1147 
1148 - (CPInteger)_dropIndexForDraggingInfo:(id)draggingInfo proposedDropOperation:(int)dropOperation
1149 {
1150  var location = [self convertPoint:[draggingInfo draggingLocation] fromView:nil],
1151  locationX = location.x + _itemSize.width / 2;
1152 
1153  var column = MIN(FLOOR(locationX / (_itemSize.width + _horizontalMargin)), _numberOfColumns),
1154  row = FLOOR(location.y / (_itemSize.height + _verticalMargin));
1155 
1156  if (row >= _numberOfRows - 1)
1157  {
1158  if (row >= _numberOfRows)
1159  {
1160  row = _numberOfRows - 1;
1161  column = _numberOfColumns;
1162  }
1163 
1164  return MIN((row * _numberOfColumns + column), [_items count]);
1165  }
1166 
1167  return (row * _numberOfColumns + column);
1168 }
1169 
1170 @end
1171 @implementation _CPCollectionViewDropIndicator : CPView
1172 {
1173  id __doxygen__;
1174 }
1175 
1176 - (void)drawRect:(CGRect)aRect
1177 {
1178  var context = [[CPGraphicsContext currentContext] graphicsPort],
1179  width = CGRectGetWidth(aRect),
1180  circleRect = CGRectMake(1, 1, width - 2, width - 2);
1181 
1182  CGContextSetStrokeColor(context, [CPColor colorWithHexString:@"4886ca"]);
1183  CGContextSetFillColor(context, [CPColor whiteColor]);
1184  CGContextSetLineWidth(context, 3);
1185 
1186  //draw white under the circle thing
1187  CGContextFillRect(context, circleRect);
1188 
1189  //draw the circle thing
1190  CGContextStrokeEllipseInRect(context, circleRect);
1191 
1192  //then draw the line
1193  CGContextBeginPath(context);
1194  CGContextMoveToPoint(context, FLOOR(width / 2), CGRectGetMinY(aRect) + width);
1195  CGContextAddLineToPoint(context, FLOOR(width / 2), CGRectGetHeight(aRect));
1196  CGContextStrokePath(context);
1197 }
1198 
1199 @end
1200 
1202 
1203 - (void)_modifySelectionWithNewIndex:(int)anIndex direction:(int)aDirection expand:(BOOL)shouldExpand
1204 {
1205  var count = [[self items] count];
1206 
1207  if (count === 0)
1208  return;
1209 
1210  anIndex = MIN(MAX(anIndex, 0), count - 1);
1211 
1212  if (_allowsMultipleSelection && shouldExpand)
1213  {
1214  var indexes = [_selectionIndexes copy],
1215  bottomAnchor = [indexes firstIndex],
1216  topAnchor = [indexes lastIndex];
1217 
1218  // if the direction is backward (-1) check with the bottom anchor
1219  if (aDirection === -1)
1220  [indexes addIndexesInRange:CPMakeRange(anIndex, bottomAnchor - anIndex + 1)];
1221  else
1222  [indexes addIndexesInRange:CPMakeRange(topAnchor, anIndex - topAnchor + 1)];
1223  }
1224  else
1225  indexes = [CPIndexSet indexSetWithIndex:anIndex];
1226 
1227  [self setSelectionIndexes:indexes];
1228  [self _scrollToSelection];
1229 }
1230 
1231 - (void)_scrollToSelection
1232 {
1233  var frame = [self frameForItemsAtIndexes:[self selectionIndexes]];
1234 
1235  if (!CGRectIsEmpty(frame))
1236  [self scrollRectToVisible:frame];
1237 }
1238 
1239 - (void)moveLeft:(id)sender
1240 {
1241  var index = [[self selectionIndexes] firstIndex];
1242  if (index === CPNotFound)
1243  index = [[self items] count];
1244 
1245  [self _modifySelectionWithNewIndex:index - 1 direction:-1 expand:NO];
1246 }
1247 
1248 - (void)moveLeftAndModifySelection:(id)sender
1249 {
1250  var index = [[self selectionIndexes] firstIndex];
1251  if (index === CPNotFound)
1252  index = [[self items] count];
1253 
1254  [self _modifySelectionWithNewIndex:index - 1 direction:-1 expand:YES];
1255 }
1256 
1257 - (void)moveRight:(id)sender
1258 {
1259  [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + 1 direction:1 expand:NO];
1260 }
1261 
1262 - (void)moveRightAndModifySelection:(id)sender
1263 {
1264  [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + 1 direction:1 expand:YES];
1265 }
1266 
1267 - (void)moveDown:(id)sender
1268 {
1269  [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + [self numberOfColumns] direction:1 expand:NO];
1270 }
1271 
1272 - (void)moveDownAndModifySelection:(id)sender
1273 {
1274  [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + [self numberOfColumns] direction:1 expand:YES];
1275 }
1276 
1277 - (void)moveUp:(id)sender
1278 {
1279  var index = [[self selectionIndexes] firstIndex];
1280  if (index == CPNotFound)
1281  index = [[self items] count];
1282 
1283  [self _modifySelectionWithNewIndex:index - [self numberOfColumns] direction:-1 expand:NO];
1284 }
1285 
1286 - (void)moveUpAndModifySelection:(id)sender
1287 {
1288  var index = [[self selectionIndexes] firstIndex];
1289  if (index == CPNotFound)
1290  index = [[self items] count];
1291 
1292  [self _modifySelectionWithNewIndex:index - [self numberOfColumns] direction:-1 expand:YES];
1293 }
1294 
1295 - (void)deleteBackward:(id)sender
1296 {
1297  if ([[self delegate] respondsToSelector:@selector(collectionView:shouldDeleteItemsAtIndexes:)])
1298  {
1299  [[self delegate] collectionView:self shouldDeleteItemsAtIndexes:[self selectionIndexes]];
1300 
1301  var index = [[self selectionIndexes] firstIndex];
1302  if (index > [[self content] count] - 1)
1303  [self setSelectionIndexes:[CPIndexSet indexSetWithIndex:[[self content] count] - 1]];
1304 
1305  [self _scrollToSelection];
1306  [self setNeedsDisplay:YES];
1307  }
1308 }
1309 
1310 - (void)keyDown:(CPEvent)anEvent
1311 {
1312  [self interpretKeyEvents:[anEvent]];
1313 }
1314 
1315 - (void)setAutoresizingMask:(unsigned)aMask
1316 {
1317  [super setAutoresizingMask:0];
1318 }
1319 
1320 @end
1321 
1323 
1324 - (CGRect)rectForItemAtIndex:(int)anIndex
1325 {
1326  _CPReportLenientDeprecation([self class], _cmd, @selector(frameForItemAtIndex:));
1327 
1328  // Don't re-compute anything just grab the current frame
1329  // This allows subclasses to override tile without messing this up.
1330  return [self frameForItemAtIndex:anIndex];
1331 }
1332 
1333 - (CGRect)rectForItemsAtIndexes:(CPIndexSet)anIndexSet
1334 {
1335  _CPReportLenientDeprecation([self class], _cmd, @selector(frameForItemsAtIndexes:));
1336 
1337  return [self frameForItemsAtIndexes:anIndexSet];
1338 }
1339 
1340 @end
1341 
1342 var CPCollectionViewMinItemSizeKey = @"CPCollectionViewMinItemSizeKey",
1343  CPCollectionViewMaxItemSizeKey = @"CPCollectionViewMaxItemSizeKey",
1344  CPCollectionViewVerticalMarginKey = @"CPCollectionViewVerticalMarginKey",
1345  CPCollectionViewMaxNumberOfRowsKey = @"CPCollectionViewMaxNumberOfRowsKey",
1346  CPCollectionViewMaxNumberOfColumnsKey = @"CPCollectionViewMaxNumberOfColumnsKey",
1347  CPCollectionViewSelectableKey = @"CPCollectionViewSelectableKey",
1348  CPCollectionViewAllowsMultipleSelectionKey = @"CPCollectionViewAllowsMultipleSelectionKey",
1349  CPCollectionViewBackgroundColorsKey = @"CPCollectionViewBackgroundColorsKey";
1350 
1351 
1353 
1354 - (id)initWithCoder:(CPCoder)aCoder
1355 {
1356  self = [super initWithCoder:aCoder];
1357 
1358  if (self)
1359  {
1360  _minItemSize = [aCoder decodeSizeForKey:CPCollectionViewMinItemSizeKey];
1361  _maxItemSize = [aCoder decodeSizeForKey:CPCollectionViewMaxItemSizeKey];
1362 
1363  _maxNumberOfRows = [aCoder decodeIntForKey:CPCollectionViewMaxNumberOfRowsKey];
1364  _maxNumberOfColumns = [aCoder decodeIntForKey:CPCollectionViewMaxNumberOfColumnsKey];
1365 
1366  _verticalMargin = [aCoder decodeFloatForKey:CPCollectionViewVerticalMarginKey];
1367 
1368  _isSelectable = [aCoder decodeBoolForKey:CPCollectionViewSelectableKey];
1369  _allowsMultipleSelection = [aCoder decodeBoolForKey:CPCollectionViewAllowsMultipleSelectionKey];
1370 
1371  [self setBackgroundColors:[aCoder decodeObjectForKey:CPCollectionViewBackgroundColorsKey]];
1372 
1373  [self _init];
1374  }
1375 
1376  return self;
1377 }
1378 
1379 - (void)encodeWithCoder:(CPCoder)aCoder
1380 {
1381  [super encodeWithCoder:aCoder];
1382 
1383  if (!CGSizeEqualToSize(_minItemSize, CGSizeMakeZero()))
1384  [aCoder encodeSize:_minItemSize forKey:CPCollectionViewMinItemSizeKey];
1385 
1386  if (!CGSizeEqualToSize(_maxItemSize, CGSizeMakeZero()))
1387  [aCoder encodeSize:_maxItemSize forKey:CPCollectionViewMaxItemSizeKey];
1388 
1389  [aCoder encodeInt:_maxNumberOfRows forKey:CPCollectionViewMaxNumberOfRowsKey];
1390  [aCoder encodeInt:_maxNumberOfColumns forKey:CPCollectionViewMaxNumberOfColumnsKey];
1391 
1392  [aCoder encodeBool:_isSelectable forKey:CPCollectionViewSelectableKey];
1393  [aCoder encodeBool:_allowsMultipleSelection forKey:CPCollectionViewAllowsMultipleSelectionKey];
1394 
1395  [aCoder encodeFloat:_verticalMargin forKey:CPCollectionViewVerticalMarginKey];
1396 
1397  [aCoder encodeObject:_backgroundColors forKey:CPCollectionViewBackgroundColorsKey];
1398 }
1399 
1400 @end
1401 
1403 
1407 - (BOOL)uniformSubviewsResizing
1408 {
1409  return _uniformSubviewsResizing;
1410 }
1411 
1415 - (void)setUniformSubviewsResizing:(BOOL)aValue
1416 {
1417  _uniformSubviewsResizing = aValue;
1418 }
1419 
1420 @end