/AppKit/CPCollectionView.j

http://github.com/cacaodev/cappuccino · Unknown · 1589 lines · 1218 code · 371 blank · 0 comment · 0 complexity · 233cb32363977371d5d3f6359551b36b MD5 · raw 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. @import <Foundation/CPArray.j>
  23. @import <Foundation/CPData.j>
  24. @import <Foundation/CPIndexSet.j>
  25. @import <Foundation/CPKeyedArchiver.j>
  26. @import <Foundation/CPKeyedUnarchiver.j>
  27. @import "CPCollectionViewItem.j"
  28. @import "CPCompatibility.j"
  29. @import "CPDragServer_Constants.j"
  30. @import "CPPasteboard.j"
  31. @import "CPView.j"
  32. @class _CPCollectionViewDropIndicator
  33. var CPCollectionViewDelegate_collectionView_acceptDrop_index_dropOperation_ = 1 << 0,
  34. CPCollectionViewDelegate_collectionView_canDragItemsAtIndexes_withEvent_ = 1 << 1,
  35. CPCollectionViewDelegate_collectionView_writeItemsAtIndexes_toPasteboard_ = 1 << 2,
  36. CPCollectionViewDelegate_collectionView_dragTypesForItemsAtIndexes_ = 1 << 3,
  37. CPCollectionViewDelegate_collectionView_dataForItemsAtIndexes_forType_ = 1 << 4,
  38. CPCollectionViewDelegate_collectionView_validateDrop_proposedIndex_dropOperation_ = 1 << 5,
  39. CPCollectionViewDelegate_collectionView_didDoubleClickOnItemAtIndex_ = 1 << 6,
  40. CPCollectionViewDelegate_collectionView_menuForItemAtIndex_ = 1 << 7,
  41. CPCollectionViewDelegate_collectionView_draggingViewForItemsAtIndexes_withEvent_offset = 1 << 8;
  42. @protocol CPCollectionViewDelegate <CPObject>
  43. @optional
  44. - (BOOL)collectionView:(CPCollectionView)collectionView acceptDrop:(id)draggingInfo index:(CPInteger)index dropOperation:(CPCollectionViewDropOperation)dropOperation;
  45. - (BOOL)collectionView:(CPCollectionView)collectionView canDragItemsAtIndexes:(CPIndexSet)indexes withEvent:(CPEvent)event;
  46. - (BOOL)collectionView:(CPCollectionView)collectionView writeItemsAtIndexes:(CPIndexSet)indexes toPasteboard:(CPPasteboard)pasteboard;
  47. - (CPArray)collectionView:(CPCollectionView)collectionView dragTypesForItemsAtIndexes:(CPIndexSet)indexes;
  48. - (CPData)collectionView:(CPCollectionView)collectionView dataForItemsAtIndexes:(CPIndexSet)indices forType:(CPString)aType;
  49. - (CPDragOperation)collectionView:(CPCollectionView)collectionView validateDrop:(id)draggingInfo proposedIndex:(CPInteger)proposedDropIndex dropOperation:(CPCollectionViewDropOperation)proposedDropOperation;
  50. - (CPMenu)collectionView:(CPCollectionView)collectionView menuForItemAtIndex:(CPInteger)anIndex;
  51. - (CPView)collectionView:(CPCollectionView)collectionView dragginViewForItemsAtIndexes:(CPIndexSet)indexes withEvent:(CPEvent)event offset:(CGPoint)dragImageOffset;
  52. - (void)collectionView:(CPCollectionView)collectionView didDoubleClickOnItemAtIndex:(int)index;
  53. @end
  54. /*!
  55. @ingroup appkit
  56. @class CPCollectionView
  57. This class displays an array as a grid of objects, where each object is represented by a view.
  58. The view is controlled by creating a CPCollectionViewItem and specifying its view, then
  59. setting that item as the collection view prototype.
  60. @par Delegate Methods
  61. @delegate - (void)collectionView:(CPCollectionView)collectionView didDoubleClickOnItemAtIndex:(int)index;
  62. Called when the user double-clicks on an item in the collection view.
  63. @param collectionView the collection view that received the double-click
  64. @param index the index of the item that received the double-click
  65. @delegate - (CPData)collectionView:(CPCollectionView)collectionView dataForItemsAtIndexes:(CPIndexSet)indices forType:(CPString)aType;
  66. Invoked to obtain data for a set of indices.
  67. @param collectionView the collection view to obtain data for
  68. @param indices the indices to return data for
  69. @param aType the data type
  70. @return a data object containing the index items
  71. @delegate - (CPArray)collectionView:(CPCollectionView)collectionView dragTypesForItemsAtIndexes:(CPIndexSet)indices;
  72. Invoked to obtain the data types supported by the specified indices for placement on the pasteboard.
  73. @param collectionView the collection view the items reside in
  74. @param indices the indices to obtain drag types
  75. @return an array of drag types (CPString)
  76. */
  77. var HORIZONTAL_MARGIN = 2;
  78. @implementation CPCollectionView : CPView
  79. {
  80. CPArray _content;
  81. CPArray _items;
  82. CPData _itemData;
  83. CPCollectionViewItem _itemPrototype;
  84. CPCollectionViewItem _itemForDragging;
  85. CPMutableArray _cachedItems;
  86. unsigned _maxNumberOfRows;
  87. unsigned _maxNumberOfColumns;
  88. CGSize _minItemSize;
  89. CGSize _maxItemSize;
  90. CPArray _backgroundColors;
  91. float _tileWidth;
  92. BOOL _isSelectable;
  93. BOOL _allowsMultipleSelection;
  94. BOOL _allowsEmptySelection;
  95. CPIndexSet _selectionIndexes;
  96. CGSize _itemSize;
  97. float _horizontalMargin;
  98. float _verticalMargin;
  99. unsigned _numberOfRows;
  100. unsigned _numberOfColumns;
  101. id <CPCollectionViewDelegate> _delegate;
  102. unsigned _implementedDelegateMethods;
  103. CPEvent _mouseDownEvent;
  104. BOOL _needsMinMaxItemSizeUpdate;
  105. CGSize _storedFrameSize;
  106. BOOL _uniformSubviewsResizing @accessors(property=uniformSubviewsResizing);
  107. BOOL _lockResizing;
  108. CPInteger _currentDropIndex;
  109. CPDragOperation _currentDragOperation;
  110. _CPCollectionViewDropIndicator _dropView;
  111. }
  112. - (id)initWithFrame:(CGRect)aFrame
  113. {
  114. self = [super initWithFrame:aFrame];
  115. if (self)
  116. {
  117. _maxNumberOfRows = 0;
  118. _maxNumberOfColumns = 0;
  119. _minItemSize = CGSizeMakeZero();
  120. _maxItemSize = CGSizeMakeZero();
  121. [self setBackgroundColors:nil];
  122. _verticalMargin = 5.0;
  123. _isSelectable = YES;
  124. _allowsEmptySelection = YES;
  125. [self _init];
  126. }
  127. return self;
  128. }
  129. - (void)_init
  130. {
  131. _content = [];
  132. _items = [];
  133. _cachedItems = [];
  134. _numberOfColumns = CPNotFound;
  135. _numberOfRows = CPNotFound;
  136. _itemSize = CGSizeMakeZero();
  137. _selectionIndexes = [CPIndexSet indexSet];
  138. _storedFrameSize = CGSizeMakeZero();
  139. _needsMinMaxItemSizeUpdate = YES;
  140. _uniformSubviewsResizing = NO;
  141. _lockResizing = NO;
  142. _currentDropIndex = -1;
  143. _currentDragOperation = CPDragOperationNone;
  144. _dropView = nil;
  145. [self setAutoresizesSubviews:NO];
  146. [self setAutoresizingMask:0];
  147. }
  148. #pragma mark -
  149. #pragma mark Delegate
  150. /*!
  151. Set the delegate of the receiver
  152. @param aDelegate the delegate object for the collectionView.
  153. */
  154. - (void)setDelegate:(id <CPCollectionViewDelegate>)aDelegate
  155. {
  156. if (_delegate === aDelegate)
  157. return;
  158. _delegate = aDelegate;
  159. _implementedDelegateMethods = 0;
  160. if ([_delegate respondsToSelector:@selector(collectionView:acceptDrop:index:dropOperation:)])
  161. _implementedDelegateMethods |= CPCollectionViewDelegate_collectionView_acceptDrop_index_dropOperation_;
  162. if ([_delegate respondsToSelector:@selector(collectionView:canDragItemsAtIndexes:withEvent:)])
  163. _implementedDelegateMethods |= CPCollectionViewDelegate_collectionView_canDragItemsAtIndexes_withEvent_;
  164. if ([_delegate respondsToSelector:@selector(collectionView:writeItemsAtIndexes:toPasteboard:)])
  165. _implementedDelegateMethods |= CPCollectionViewDelegate_collectionView_writeItemsAtIndexes_toPasteboard_;
  166. if ([_delegate respondsToSelector:@selector(collectionView:dragTypesForItemsAtIndexes:)])
  167. _implementedDelegateMethods |= CPCollectionViewDelegate_collectionView_dragTypesForItemsAtIndexes_;
  168. if ([_delegate respondsToSelector:@selector(collectionView:dataForItemsAtIndexes:forType:)])
  169. _implementedDelegateMethods |= CPCollectionViewDelegate_collectionView_dataForItemsAtIndexes_forType_;
  170. if ([_delegate respondsToSelector:@selector(collectionView:validateDrop:proposedIndex:dropOperation:)])
  171. _implementedDelegateMethods |= CPCollectionViewDelegate_collectionView_validateDrop_proposedIndex_dropOperation_;
  172. if ([_delegate respondsToSelector:@selector(collectionView:didDoubleClickOnItemAtIndex:)])
  173. _implementedDelegateMethods |= CPCollectionViewDelegate_collectionView_didDoubleClickOnItemAtIndex_;
  174. if ([_delegate respondsToSelector:@selector(collectionView:menuForItemAtIndex:)])
  175. _implementedDelegateMethods |= CPCollectionViewDelegate_collectionView_menuForItemAtIndex_;
  176. if ([_delegate respondsToSelector:@selector(collectionView:draggingViewForItemsAtIndexes:withEvent:offset:)])
  177. _implementedDelegateMethods |= CPCollectionViewDelegate_collectionView_draggingViewForItemsAtIndexes_withEvent_offset;
  178. }
  179. /*!
  180. Sets the item prototype to \c anItem
  181. @param anItem the new item prototype.
  182. @note
  183. - If anItem is located in an external cib file, representedObject, outlets, and bindings will be automatically restored when an item is created.
  184. - If anItem and its view belong to the same cib as the collection view, the item prototype should implement the CPCoding protocol because the item is copied by archiving and unarchiving the prototypal view.
  185. @note
  186. Bindings won't be restored through archiving, instead you need to subclass the -representedObject: method and update the view there.
  187. @par Example:
  188. @code
  189. @implementation MyCustomPrototypeItem: CPCollectionViewItem
  190. {
  191. @outlet CPTextField textField;
  192. }
  193. - (id)initWithCoder:(CPCoder)aCoder
  194. {
  195. self = [super initWithCoder:aCoder];
  196. textField = [aCoder decodeObjectForKey:@"TextField"];
  197. return self;
  198. }
  199. - (void)encodeWithCoder:(CPCoder)aCoder
  200. {
  201. [super encodeWithCoder:aCoder];
  202. [aCoder encodeConditionalObject:textField forKey:@"TextField"];
  203. }
  204. - (void)setRepresentedObject:(id)anObject
  205. {
  206. [super setRepresentedObject:anObject];
  207. [textField setStringValue:[anObject objectForKey:@"value"]];
  208. [[self view] setColor:[anObject objectForKey:@"color"]];
  209. }
  210. @end
  211. @endcode
  212. */
  213. - (void)setItemPrototype:(CPCollectionViewItem)anItem
  214. {
  215. _cachedItems = [];
  216. _itemData = nil;
  217. _itemForDragging = nil;
  218. _itemPrototype = anItem;
  219. [self _reloadContentCachingRemovedItems:NO];
  220. }
  221. /*!
  222. Returns the current item prototype
  223. */
  224. - (CPCollectionViewItem)itemPrototype
  225. {
  226. return _itemPrototype;
  227. }
  228. /*!
  229. Returns a collection view item for \c anObject.
  230. @param anObject the object to be represented.
  231. */
  232. - (CPCollectionViewItem)newItemForRepresentedObject:(id)anObject
  233. {
  234. var item = nil;
  235. if (_cachedItems.length)
  236. item = _cachedItems.pop();
  237. else
  238. item = [_itemPrototype copy];
  239. [item setRepresentedObject:anObject];
  240. [[item view] setFrameSize:_itemSize];
  241. return item;
  242. }
  243. // Working with the Responder Chain
  244. /*!
  245. Returns \c YES by default.
  246. */
  247. - (BOOL)acceptsFirstResponder
  248. {
  249. return YES;
  250. }
  251. /*!
  252. Returns whether the receiver is currently the first responder.
  253. */
  254. - (BOOL)isFirstResponder
  255. {
  256. return [[self window] firstResponder] === self;
  257. }
  258. // Setting the Content
  259. /*!
  260. Sets the content of the collection view to the content in \c anArray.
  261. This array can be of any type, and each element will be passed to the \c -setRepresentedObject: method.
  262. It's the responsibility of your custom collection view item to interpret the object.
  263. If the new content array is smaller than the previous one, note that [receiver selectionIndexes] may
  264. refer to out of range indices. \c selectionIndexes is not changed as a result of calling the
  265. \c setContent: method.
  266. @param anArray a content array
  267. */
  268. - (void)setContent:(CPArray)anArray
  269. {
  270. _content = anArray;
  271. [self reloadContent];
  272. }
  273. /*!
  274. Returns the collection view content array
  275. */
  276. - (CPArray)content
  277. {
  278. return _content;
  279. }
  280. /*!
  281. Returns the collection view items.
  282. */
  283. - (CPArray)items
  284. {
  285. return _items;
  286. }
  287. // Setting the Selection Mode
  288. /*!
  289. Sets whether the user is allowed to select items
  290. @param isSelectable \c YES allows the user to select items.
  291. */
  292. - (void)setSelectable:(BOOL)isSelectable
  293. {
  294. if (_isSelectable == isSelectable)
  295. return;
  296. _isSelectable = isSelectable;
  297. if (!_isSelectable)
  298. {
  299. var index = CPNotFound,
  300. itemCount = [_items count];
  301. // Be wary of invalid selection ranges since setContent: does not clear selection indexes.
  302. while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound && index < itemCount)
  303. [_items[index] setSelected:NO];
  304. }
  305. }
  306. /*!
  307. Returns \c YES if the collection view is
  308. selectable, and \c NO otherwise.
  309. */
  310. - (BOOL)isSelectable
  311. {
  312. return _isSelectable;
  313. }
  314. /*!
  315. Sets whether the user may have no items selected. If YES, mouse clicks not on any item will empty the current selection. The first item will also start off as selected.
  316. @param shouldAllowMultipleSelection \c YES allows the user to select multiple items
  317. */
  318. - (void)setAllowsEmptySelection:(BOOL)shouldAllowEmptySelection
  319. {
  320. _allowsEmptySelection = shouldAllowEmptySelection;
  321. }
  322. /*!
  323. Returns \c YES if the user can select no items, \c NO otherwise.
  324. */
  325. - (BOOL)allowsEmptySelection
  326. {
  327. return _allowsEmptySelection;
  328. }
  329. /*!
  330. Sets whether the user can select multiple items.
  331. @param shouldAllowMultipleSelection \c YES allows the user to select multiple items
  332. */
  333. - (void)setAllowsMultipleSelection:(BOOL)shouldAllowMultipleSelection
  334. {
  335. _allowsMultipleSelection = shouldAllowMultipleSelection;
  336. }
  337. /*!
  338. Returns \c YES if the user can select multiple items, \c NO otherwise.
  339. */
  340. - (BOOL)allowsMultipleSelection
  341. {
  342. return _allowsMultipleSelection;
  343. }
  344. /*!
  345. Sets the selected items based on the provided indices.
  346. @param anIndexSet the set of items to be selected
  347. */
  348. - (void)setSelectionIndexes:(CPIndexSet)anIndexSet
  349. {
  350. if (!anIndexSet)
  351. anIndexSet = [CPIndexSet indexSet];
  352. if (!_isSelectable || [_selectionIndexes isEqual:anIndexSet])
  353. return;
  354. var index = CPNotFound,
  355. itemCount = [_items count];
  356. // Be wary of invalid selection ranges since setContent: does not clear selection indexes.
  357. while ((index = [_selectionIndexes indexGreaterThanIndex:index]) !== CPNotFound && index < itemCount)
  358. [_items[index] setSelected:NO];
  359. _selectionIndexes = anIndexSet;
  360. var index = CPNotFound;
  361. while ((index = [_selectionIndexes indexGreaterThanIndex:index]) !== CPNotFound)
  362. [_items[index] setSelected:YES];
  363. var binderClass = [[self class] _binderClassForBinding:@"selectionIndexes"];
  364. [[binderClass getBinding:@"selectionIndexes" forObject:self] reverseSetValueFor:@"selectionIndexes"];
  365. }
  366. /*!
  367. Returns a set of the selected indices.
  368. */
  369. - (CPIndexSet)selectionIndexes
  370. {
  371. return [_selectionIndexes copy];
  372. }
  373. - (void)reloadContent
  374. {
  375. [self _reloadContentCachingRemovedItems:YES];
  376. }
  377. /* @ignore */
  378. - (void)_reloadContentCachingRemovedItems:(BOOL)shouldCache
  379. {
  380. // Remove current views
  381. var count = _items.length;
  382. while (count--)
  383. {
  384. [[_items[count] view] removeFromSuperview];
  385. [_items[count] setSelected:NO];
  386. if (shouldCache)
  387. _cachedItems.push(_items[count]);
  388. }
  389. _items = [];
  390. if (!_itemPrototype)
  391. return;
  392. var index = 0;
  393. count = _content.length;
  394. for (; index < count; ++index)
  395. {
  396. _items.push([self newItemForRepresentedObject:_content[index]]);
  397. [self addSubview:[_items[index] view]];
  398. }
  399. index = CPNotFound;
  400. // Be wary of invalid selection ranges since setContent: does not clear selection indexes.
  401. while ((index = [_selectionIndexes indexGreaterThanIndex:index]) != CPNotFound && index < count)
  402. [_items[index] setSelected:YES];
  403. [self tileIfNeeded:NO];
  404. }
  405. - (void)resizeSubviewsWithOldSize:(CGSize)oldBoundsSize
  406. {
  407. // Desactivate subviews autoresizing
  408. }
  409. - (void)resizeWithOldSuperviewSize:(CGSize)oldBoundsSize
  410. {
  411. if (_lockResizing)
  412. return;
  413. _lockResizing = YES;
  414. [self tile];
  415. _lockResizing = NO;
  416. }
  417. - (void)tile
  418. {
  419. [self tileIfNeeded:!_uniformSubviewsResizing];
  420. }
  421. - (void)tileIfNeeded:(BOOL)lazyFlag
  422. {
  423. var frameSize = [[self superview] frameSize],
  424. count = _items.length,
  425. oldNumberOfColumns = _numberOfColumns,
  426. oldNumberOfRows = _numberOfRows,
  427. oldItemSize = _itemSize,
  428. storedFrameSize = _storedFrameSize;
  429. // No need to tile if we are not yet placed in the view hierarchy.
  430. if (!frameSize)
  431. return;
  432. [self _updateMinMaxItemSizeIfNeeded];
  433. [self _computeGridWithSize:frameSize count:@ref(count)];
  434. //CPLog.debug("frameSize="+CPStringFromSize(frameSize) + "itemSize="+CPStringFromSize(itemSize) + " ncols=" + colsRowsCount[0] +" nrows="+ colsRowsCount[1]+" displayCount="+ colsRowsCount[2]);
  435. [self setFrameSize:_storedFrameSize];
  436. //CPLog.debug("OLD " + oldNumberOfColumns + " NEW " + _numberOfColumns);
  437. if (!lazyFlag ||
  438. _numberOfColumns !== oldNumberOfColumns ||
  439. _numberOfRows !== oldNumberOfRows ||
  440. !CGSizeEqualToSize(_itemSize, oldItemSize))
  441. [self displayItems:_items frameSize:_storedFrameSize itemSize:_itemSize columns:_numberOfColumns rows:_numberOfRows count:count];
  442. }
  443. - (void)_computeGridWithSize:(CGSize)aSuperviewSize count:(Function)countRef
  444. {
  445. var width = aSuperviewSize.width,
  446. height = aSuperviewSize.height,
  447. itemSize = CGSizeMakeCopy(_minItemSize),
  448. maxItemSizeWidth = _maxItemSize.width,
  449. maxItemSizeHeight = _maxItemSize.height,
  450. itemsCount = [_items count],
  451. numberOfRows,
  452. numberOfColumns;
  453. numberOfColumns = FLOOR(width / itemSize.width);
  454. if (maxItemSizeWidth == 0)
  455. numberOfColumns = MIN(numberOfColumns, _maxNumberOfColumns);
  456. if (_maxNumberOfColumns > 0)
  457. numberOfColumns = MIN(MIN(_maxNumberOfColumns, itemsCount), numberOfColumns);
  458. numberOfColumns = MAX(1.0, numberOfColumns);
  459. itemSize.width = FLOOR(width / numberOfColumns);
  460. if (maxItemSizeWidth > 0)
  461. {
  462. itemSize.width = MIN(maxItemSizeWidth, itemSize.width);
  463. if (numberOfColumns == 1)
  464. itemSize.width = MIN(maxItemSizeWidth, width);
  465. }
  466. numberOfRows = CEIL(itemsCount / numberOfColumns);
  467. if (_maxNumberOfRows > 0)
  468. numberOfRows = MIN(numberOfRows, _maxNumberOfRows);
  469. height = MAX(height, numberOfRows * (_minItemSize.height + _verticalMargin));
  470. var itemSizeHeight = FLOOR(height / numberOfRows) - _verticalMargin;
  471. if (maxItemSizeHeight > 0)
  472. itemSizeHeight = MIN(itemSizeHeight, maxItemSizeHeight);
  473. _itemSize = CGSizeMake(MAX(_minItemSize.width, itemSize.width), MAX(_minItemSize.height, itemSizeHeight));
  474. _storedFrameSize = CGSizeMake(MAX(width, _minItemSize.width), height);
  475. _numberOfColumns = numberOfColumns;
  476. _numberOfRows = numberOfRows;
  477. countRef(MIN(itemsCount, numberOfColumns * numberOfRows));
  478. }
  479. - (void)displayItems:(CPArray)displayItems frameSize:(CGSize)aFrameSize itemSize:(CGSize)anItemSize columns:(CPInteger)numberOfColumns rows:(CPInteger)numberOfRows count:(CPInteger)displayCount
  480. {
  481. // CPLog.debug("DISPLAY ITEMS " + numberOfColumns + " " + numberOfRows);
  482. _horizontalMargin = _uniformSubviewsResizing ? FLOOR((aFrameSize.width - numberOfColumns * anItemSize.width) / (numberOfColumns + 1)) : HORIZONTAL_MARGIN;
  483. var x = _horizontalMargin,
  484. y = -anItemSize.height;
  485. [displayItems enumerateObjectsUsingBlock:function(item, idx, stop)
  486. {
  487. var view = [item view];
  488. if (idx >= displayCount)
  489. {
  490. [view setFrameOrigin:CGPointMake(-anItemSize.width, -anItemSize.height)];
  491. return;
  492. }
  493. if (idx % numberOfColumns == 0)
  494. {
  495. x = _horizontalMargin;
  496. y += _verticalMargin + anItemSize.height;
  497. }
  498. [view setFrameOrigin:CGPointMake(x, y)];
  499. [view setFrameSize:anItemSize];
  500. x += anItemSize.width + _horizontalMargin;
  501. }];
  502. }
  503. - (void)_updateMinMaxItemSizeIfNeeded
  504. {
  505. if (!_needsMinMaxItemSizeUpdate)
  506. return;
  507. var prototypeView;
  508. if (_itemPrototype && (prototypeView = [_itemPrototype view]))
  509. {
  510. if (_minItemSize.width == 0)
  511. _minItemSize.width = [prototypeView frameSize].width;
  512. if (_minItemSize.height == 0)
  513. _minItemSize.height = [prototypeView frameSize].height;
  514. if (_maxItemSize.height == 0 && !([prototypeView autoresizingMask] & CPViewHeightSizable))
  515. _maxItemSize.height = [prototypeView frameSize].height;
  516. if (_maxItemSize.width == 0 && !([prototypeView autoresizingMask] & CPViewWidthSizable))
  517. _maxItemSize.width = [prototypeView frameSize].width;
  518. _needsMinMaxItemSizeUpdate = NO;
  519. }
  520. }
  521. // Laying Out the Collection View
  522. /*!
  523. Sets the maximum number of rows.
  524. @param aMaxNumberOfRows the new maximum number of rows
  525. */
  526. - (void)setMaxNumberOfRows:(unsigned)aMaxNumberOfRows
  527. {
  528. if (_maxNumberOfRows == aMaxNumberOfRows)
  529. return;
  530. _maxNumberOfRows = aMaxNumberOfRows;
  531. [self tile];
  532. }
  533. /*!
  534. Returns the maximum number of rows.
  535. */
  536. - (unsigned)maxNumberOfRows
  537. {
  538. return _maxNumberOfRows;
  539. }
  540. /*!
  541. Sets the maximum number of columns.
  542. @param aMaxNumberOfColumns the new maximum number of columns
  543. */
  544. - (void)setMaxNumberOfColumns:(unsigned)aMaxNumberOfColumns
  545. {
  546. if (_maxNumberOfColumns == aMaxNumberOfColumns)
  547. return;
  548. _maxNumberOfColumns = aMaxNumberOfColumns;
  549. [self tile];
  550. }
  551. /*!
  552. Returns the maximum number of columns
  553. */
  554. - (unsigned)maxNumberOfColumns
  555. {
  556. return _maxNumberOfColumns;
  557. }
  558. /*!
  559. Returns the current number of rows
  560. */
  561. - (unsigned)numberOfRows
  562. {
  563. return _numberOfRows;
  564. }
  565. /*!
  566. Returns the current number of columns
  567. */
  568. - (unsigned)numberOfColumns
  569. {
  570. return _numberOfColumns;
  571. }
  572. /*!
  573. Sets the minimum size for an item
  574. @param aSize the new minimum item size
  575. */
  576. - (void)setMinItemSize:(CGSize)aSize
  577. {
  578. if (aSize === nil || aSize === undefined)
  579. [CPException raise:CPInvalidArgumentException reason:"Invalid value provided for minimum size"];
  580. if (CGSizeEqualToSize(_minItemSize, aSize))
  581. return;
  582. _minItemSize = CGSizeMakeCopy(aSize);
  583. if (CGSizeEqualToSize(_minItemSize, CGSizeMakeZero()))
  584. _needsMinMaxItemSizeUpdate = YES;
  585. [self tile];
  586. }
  587. /*!
  588. Returns the current minimum item size
  589. */
  590. - (CGSize)minItemSize
  591. {
  592. return _minItemSize;
  593. }
  594. /*!
  595. Sets the maximum item size.
  596. @param aSize the new maximum item size
  597. */
  598. - (void)setMaxItemSize:(CGSize)aSize
  599. {
  600. if (CGSizeEqualToSize(_maxItemSize, aSize))
  601. return;
  602. _maxItemSize = CGSizeMakeCopy(aSize);
  603. // if (_maxItemSize.width == 0 || _maxItemSize.height == 0)
  604. // _needsMinMaxItemSizeUpdate = YES;
  605. [self tile];
  606. }
  607. /*!
  608. Returns the current maximum item size.
  609. */
  610. - (CGSize)maxItemSize
  611. {
  612. return _maxItemSize;
  613. }
  614. - (void)setBackgroundColors:(CPArray)backgroundColors
  615. {
  616. if (_backgroundColors === backgroundColors)
  617. return;
  618. _backgroundColors = backgroundColors;
  619. if (!_backgroundColors)
  620. _backgroundColors = [[CPColor whiteColor]];
  621. if ([_backgroundColors count] === 1)
  622. [self setBackgroundColor:_backgroundColors[0]];
  623. else
  624. [self setBackgroundColor:nil];
  625. [self setNeedsDisplay:YES];
  626. }
  627. - (CPArray)backgroundColors
  628. {
  629. return _backgroundColors;
  630. }
  631. - (void)mouseUp:(CPEvent)anEvent
  632. {
  633. if ([_selectionIndexes count] && [anEvent clickCount] == 2)
  634. [self _sendDelegateDidDoubleClickOnItemAtIndex:[_selectionIndexes firstIndex]];
  635. }
  636. - (void)mouseDown:(CPEvent)anEvent
  637. {
  638. _mouseDownEvent = anEvent;
  639. var location = [self convertPoint:[anEvent locationInWindow] fromView:nil],
  640. index = [self _indexAtPoint:location];
  641. if (index >= 0 && index < _items.length)
  642. {
  643. if (_allowsMultipleSelection && ([anEvent modifierFlags] & CPPlatformActionKeyMask || [anEvent modifierFlags] & CPShiftKeyMask))
  644. {
  645. if ([anEvent modifierFlags] & CPPlatformActionKeyMask)
  646. {
  647. var indexes = [_selectionIndexes copy];
  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. // This catches the case where the shift key is held down for the first selection.
  658. if (firstSelectedIndex === CPNotFound)
  659. firstSelectedIndex = index;
  660. if (index < firstSelectedIndex)
  661. newSelectedRange = CPMakeRange(index, (firstSelectedIndex - index) + 1);
  662. else
  663. newSelectedRange = CPMakeRange(firstSelectedIndex, (index - firstSelectedIndex) + 1);
  664. indexes = [[self selectionIndexes] copy];
  665. [indexes addIndexesInRange:newSelectedRange];
  666. }
  667. }
  668. else
  669. indexes = [CPIndexSet indexSetWithIndex:index];
  670. [self setSelectionIndexes:indexes];
  671. // TODO Is it allowable for collection view items to become the first responder? In that case they
  672. // may have become that at this point by virtue of CPWindow's sendEvent: mouse down handling, and
  673. // the following line will rudely snatch it away from them. For most cases though, clicking on an
  674. // item should naturally make the collection view the first responder so that keyboard navigation
  675. // is enabled.
  676. [[self window] makeFirstResponder:self];
  677. }
  678. else if (_allowsEmptySelection)
  679. [self setSelectionIndexes:[CPIndexSet indexSet]];
  680. }
  681. // Cappuccino Additions
  682. /*!
  683. Sets the collection view's vertical spacing between elements.
  684. @param aVerticalMargin the number of pixels to place between elements
  685. */
  686. - (void)setVerticalMargin:(float)aVerticalMargin
  687. {
  688. if (_verticalMargin == aVerticalMargin)
  689. return;
  690. _verticalMargin = aVerticalMargin;
  691. [self tile];
  692. }
  693. - (void)setUniformSubviewsResizing:(BOOL)flag
  694. {
  695. _uniformSubviewsResizing = flag;
  696. [self tileIfNeeded:NO];
  697. }
  698. /*!
  699. Gets the collection view's current vertical spacing between elements.
  700. */
  701. - (float)verticalMargin
  702. {
  703. return _verticalMargin;
  704. }
  705. /*!
  706. Returns the collection view's delegate
  707. */
  708. - (id)delegate
  709. {
  710. return _delegate;
  711. }
  712. /*!
  713. @ignore
  714. */
  715. - (CPMenu)menuForEvent:(CPEvent)theEvent
  716. {
  717. if (![self _delegateRespondsToCollectionViewMenuForItemAtIndex])
  718. return [super menuForEvent:theEvent];
  719. var location = [self convertPoint:[theEvent locationInWindow] fromView:nil],
  720. index = [self _indexAtPoint:location];
  721. return [self _sendDelegateMenuForItemAtIndex:index];
  722. }
  723. - (int)_indexAtPoint:(CGPoint)thePoint
  724. {
  725. var column = FLOOR(thePoint.x / (_itemSize.width + _horizontalMargin));
  726. if (column < _numberOfColumns)
  727. {
  728. var row = FLOOR(thePoint.y / (_itemSize.height + _verticalMargin));
  729. if (row < _numberOfRows)
  730. return (row * _numberOfColumns + column);
  731. }
  732. return CPNotFound;
  733. }
  734. - (CPCollectionViewItem)itemAtIndex:(CPUInteger)anIndex
  735. {
  736. return [_items objectAtIndex:anIndex];
  737. }
  738. - (CGRect)frameForItemAtIndex:(CPUInteger)anIndex
  739. {
  740. return [[[self itemAtIndex:anIndex] view] frame];
  741. }
  742. - (CGRect)frameForItemsAtIndexes:(CPIndexSet)anIndexSet
  743. {
  744. var indexArray = [],
  745. frame = CGRectNull;
  746. [anIndexSet getIndexes:indexArray maxCount:-1 inIndexRange:nil];
  747. var index = 0,
  748. count = [indexArray count];
  749. for (; index < count; ++index)
  750. frame = CGRectUnion(frame, [self frameForItemAtIndex:indexArray[index]]);
  751. return frame;
  752. }
  753. @end
  754. @implementation CPCollectionView (DragAndDrop)
  755. /*
  756. TODO: dropOperation is not supported yet. The visible drop operation is like CPCollectionViewDropBefore.
  757. */
  758. /*!
  759. Places the selected items on the specified pasteboard. The items are requested from the collection's delegate.
  760. @param aPasteboard the pasteboard to put the items on
  761. @param aType the format the pasteboard data
  762. */
  763. - (void)pasteboard:(CPPasteboard)aPasteboard provideDataForType:(CPString)aType
  764. {
  765. [aPasteboard setData:[self _sendDelegateDataForItemsAtIndexes:_selectionIndexes forType:aType] forType:aType];
  766. }
  767. - (void)_createDropIndicatorIfNeeded
  768. {
  769. // Create and position the drop indicator view.
  770. if (!_dropView)
  771. _dropView = [[_CPCollectionViewDropIndicator alloc] initWithFrame:CGRectMake(-8, -8, 0, 0)];
  772. [_dropView setFrameSize:CGSizeMake(10, _itemSize.height + _verticalMargin)];
  773. [self addSubview:_dropView];
  774. }
  775. - (void)mouseDragged:(CPEvent)anEvent
  776. {
  777. // Don't crash if we never registered the intial click.
  778. if (!_mouseDownEvent)
  779. return;
  780. [self _createDropIndicatorIfNeeded];
  781. var locationInWindow = [anEvent locationInWindow],
  782. mouseDownLocationInWindow = [_mouseDownEvent locationInWindow];
  783. // FIXME: This is because Safari's drag hysteresis is 3px x 3px
  784. if ((ABS(locationInWindow.x - mouseDownLocationInWindow.x) < 3) &&
  785. (ABS(locationInWindow.y - mouseDownLocationInWindow.y) < 3))
  786. return;
  787. if (![self _delegateRespondsToCollectionViewDragTypesForItemsAtIndexes])
  788. return;
  789. // If we don't have any selected items, we've clicked away, and thus the drag is meaningless.
  790. if (![_selectionIndexes count])
  791. return;
  792. if (![self _sendDelegateCanDragItemsAtIndexes:_selectionIndexes withEvent:_mouseDownEvent])
  793. return;
  794. // Set up the pasteboard
  795. var dragTypes = [self _sendDelegateDragTypesForItemsAtIndexes:_selectionIndexes];
  796. [[CPPasteboard pasteboardWithName:CPDragPboard] declareTypes:dragTypes owner:self];
  797. var dragImageOffset = CGSizeMakeZero(),
  798. view = [self _sendDelegateDraggingViewForItemsAtIndexes:_selectionIndexes withEvent:_mouseDownEvent offset:dragImageOffset];
  799. [view setFrameSize:_itemSize];
  800. [view setAlphaValue:0.7];
  801. var dragLocation = [self convertPoint:locationInWindow fromView:nil],
  802. dragPoint = CGPointMake(dragLocation.x - _itemSize.width / 2 , dragLocation.y - _itemSize.height / 2);
  803. [self dragView:view
  804. at:dragPoint
  805. offset:dragImageOffset
  806. event:_mouseDownEvent
  807. pasteboard:nil
  808. source:self
  809. slideBack:YES];
  810. }
  811. - (CPView)draggingViewForItemsAtIndexes:(CPIndexSet)indexes withEvent:(CPEvent)event offset:(CGPoint)dragImageOffset
  812. {
  813. var idx = _content[[indexes firstIndex]];
  814. if (!_itemForDragging)
  815. _itemForDragging = [self newItemForRepresentedObject:idx];
  816. else
  817. [_itemForDragging setRepresentedObject:idx];
  818. return [_itemForDragging view];
  819. }
  820. - (CPDragOperation)draggingEntered:(id)draggingInfo
  821. {
  822. var dropIndex = -1,
  823. dropIndexRef = @ref(dropIndex),
  824. dragOp = [self _validateDragWithInfo:draggingInfo dropIndex:dropIndexRef dropOperation:1];
  825. dropIndex = dropIndexRef();
  826. [self _createDropIndicatorIfNeeded];
  827. [self _updateDragAndDropStateWithDraggingInfo:draggingInfo newDragOperation:dragOp newDropIndex:dropIndex newDropOperation:1];
  828. return _currentDragOperation;
  829. }
  830. - (CPDragOperation)draggingUpdated:(id)draggingInfo
  831. {
  832. if (![self _dropIndexDidChange:draggingInfo])
  833. return _currentDragOperation;
  834. var dropIndex,
  835. dropIndexRef = @ref(dropIndex);
  836. var dragOperation = [self _validateDragWithInfo:draggingInfo dropIndex:dropIndexRef dropOperation:1];
  837. dropIndex = dropIndexRef();
  838. [self _updateDragAndDropStateWithDraggingInfo:draggingInfo newDragOperation:dragOperation newDropIndex:dropIndex newDropOperation:1];
  839. return dragOperation;
  840. }
  841. - (CPDragOperation)_validateDragWithInfo:(id)draggingInfo dropIndex:(Function)dropIndexRef dropOperation:(int)dropOperation
  842. {
  843. var result = CPDragOperationMove,
  844. dropIndex = [self _dropIndexForDraggingInfo:draggingInfo proposedDropOperation:dropOperation];
  845. if ([self _delegateRespondsToCollectionViewValidateDropProposedIndexDropOperation])
  846. {
  847. var dropIndexRef2 = @ref(dropIndex);
  848. result = [self _sendDelegateValidateDrop:draggingInfo proposedIndex:dropIndexRef2 dropOperation:dropOperation];
  849. if (result !== CPDragOperationNone)
  850. dropIndex = dropIndexRef2();
  851. }
  852. dropIndexRef(dropIndex);
  853. return result;
  854. }
  855. - (void)draggingExited:(id)draggingInfo
  856. {
  857. [self _updateDragAndDropStateWithDraggingInfo:draggingInfo newDragOperation:0 newDropIndex:-1 newDropOperation:1];
  858. }
  859. - (void)draggingEnded:(id)draggingInfo
  860. {
  861. [self _updateDragAndDropStateWithDraggingInfo:draggingInfo newDragOperation:0 newDropIndex:-1 newDropOperation:1];
  862. }
  863. /*
  864. Not supported. Use -collectionView:dataForItemsAtIndexes:fortype:
  865. - (BOOL)_writeItemsAtIndexes:(CPIndexSet)indexes toPasteboard:(CPPasteboard)pboard
  866. {
  867. if ([self respondsToSelector:@selector(collectionView:writeItemsAtIndexes:toPasteboard:)])
  868. return [_delegate collectionView:self writeItemsAtIndexes:indexes toPasteboard:pboard];
  869. return NO;
  870. }
  871. */
  872. - (BOOL)performDragOperation:(id)draggingInfo
  873. {
  874. var result = NO;
  875. if (_currentDragOperation && _currentDropIndex !== -1)
  876. result = [self _sendDelegateAcceptDrop:draggingInfo index:_currentDropIndex dropOperation:1];
  877. [self draggingEnded:draggingInfo]; // Is this correct ?
  878. return result;
  879. }
  880. - (void)_updateDragAndDropStateWithDraggingInfo:(id)draggingInfo newDragOperation:(CPDragOperation)dragOperation newDropIndex:(CPInteger)dropIndex newDropOperation:(CPInteger)dropOperation
  881. {
  882. _currentDropIndex = dropIndex;
  883. _currentDragOperation = dragOperation;
  884. var frameOrigin,
  885. dropviewFrameWidth = CGRectGetWidth([_dropView frame]);
  886. if (_currentDropIndex == -1 || _currentDragOperation == CPDragOperationNone)
  887. frameOrigin = CGPointMake(-dropviewFrameWidth, 0);
  888. else if (_currentDropIndex == 0)
  889. frameOrigin = CGPointMake(0, 0);
  890. else
  891. {
  892. var offset;
  893. if ((_currentDropIndex % _numberOfColumns) !== 0 || _currentDropIndex == [_items count])
  894. {
  895. dropIndex = _currentDropIndex - 1;
  896. offset = (_horizontalMargin - dropviewFrameWidth) / 2;
  897. }
  898. else
  899. {
  900. offset = - _itemSize.width - dropviewFrameWidth - (_horizontalMargin - dropviewFrameWidth) / 2;
  901. }
  902. var rect = [self frameForItemAtIndex:dropIndex];
  903. frameOrigin = CGPointMake(CGRectGetMaxX(rect) + offset, rect.origin.y - _verticalMargin);
  904. }
  905. [_dropView setFrameOrigin:frameOrigin];
  906. }
  907. - (BOOL)_dropIndexDidChange:(id)draggingInfo
  908. {
  909. var dropIndex = [self _dropIndexForDraggingInfo:draggingInfo proposedDropOperation:1];
  910. if (dropIndex == CPNotFound)
  911. dropIndex = [[self content] count];
  912. return (_currentDropIndex !== dropIndex)
  913. }
  914. - (CPInteger)_dropIndexForDraggingInfo:(id)draggingInfo proposedDropOperation:(int)dropOperation
  915. {
  916. var location = [self convertPoint:[draggingInfo draggingLocation] fromView:nil],
  917. locationX = location.x + _itemSize.width / 2;
  918. var column = MIN(FLOOR(locationX / (_itemSize.width + _horizontalMargin)), _numberOfColumns),
  919. row = FLOOR(location.y / (_itemSize.height + _verticalMargin));
  920. if (row >= _numberOfRows - 1)
  921. {
  922. if (row >= _numberOfRows)
  923. {
  924. row = _numberOfRows - 1;
  925. column = _numberOfColumns;
  926. }
  927. return MIN((row * _numberOfColumns + column), [_items count]);
  928. }
  929. return (row * _numberOfColumns + column);
  930. }
  931. @end
  932. @implementation _CPCollectionViewDropIndicator : CPView
  933. {
  934. }
  935. - (void)drawRect:(CGRect)aRect
  936. {
  937. var context = [[CPGraphicsContext currentContext] graphicsPort],
  938. width = CGRectGetWidth(aRect),
  939. circleRect = CGRectMake(1, 1, width - 2, width - 2);
  940. CGContextSetStrokeColor(context, [CPColor colorWithHexString:@"4886ca"]);
  941. CGContextSetFillColor(context, [CPColor whiteColor]);
  942. CGContextSetLineWidth(context, 3);
  943. //draw white under the circle thing
  944. CGContextFillRect(context, circleRect);
  945. //draw the circle thing
  946. CGContextStrokeEllipseInRect(context, circleRect);
  947. //then draw the line
  948. CGContextBeginPath(context);
  949. CGContextMoveToPoint(context, FLOOR(width / 2), CGRectGetMinY(aRect) + width);
  950. CGContextAddLineToPoint(context, FLOOR(width / 2), CGRectGetHeight(aRect));
  951. CGContextStrokePath(context);
  952. }
  953. @end
  954. @implementation CPCollectionView (KeyboardInteraction)
  955. - (void)_modifySelectionWithNewIndex:(int)anIndex direction:(int)aDirection expand:(BOOL)shouldExpand
  956. {
  957. var count = [[self items] count];
  958. if (count === 0)
  959. return;
  960. anIndex = MIN(MAX(anIndex, 0), count - 1);
  961. if (_allowsMultipleSelection && shouldExpand)
  962. {
  963. var indexes = [_selectionIndexes copy],
  964. bottomAnchor = [indexes firstIndex],
  965. topAnchor = [indexes lastIndex];
  966. // if the direction is backward (-1) check with the bottom anchor
  967. if (aDirection === -1)
  968. [indexes addIndexesInRange:CPMakeRange(anIndex, bottomAnchor - anIndex + 1)];
  969. else
  970. [indexes addIndexesInRange:CPMakeRange(topAnchor, anIndex - topAnchor + 1)];
  971. }
  972. else
  973. indexes = [CPIndexSet indexSetWithIndex:anIndex];
  974. [self setSelectionIndexes:indexes];
  975. [self _scrollToSelection];
  976. }
  977. - (void)_scrollToSelection
  978. {
  979. var frame = [self frameForItemsAtIndexes:[self selectionIndexes]];
  980. if (!CGRectIsEmpty(frame))
  981. [self scrollRectToVisible:frame];
  982. }
  983. - (void)moveLeft:(id)sender
  984. {
  985. var index = [[self selectionIndexes] firstIndex];
  986. if (index === CPNotFound)
  987. index = [[self items] count];
  988. [self _modifySelectionWithNewIndex:index - 1 direction:-1 expand:NO];
  989. }
  990. - (void)moveLeftAndModifySelection:(id)sender
  991. {
  992. var index = [[self selectionIndexes] firstIndex];
  993. if (index === CPNotFound)
  994. index = [[self items] count];
  995. [self _modifySelectionWithNewIndex:index - 1 direction:-1 expand:YES];
  996. }
  997. - (void)moveRight:(id)sender
  998. {
  999. [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + 1 direction:1 expand:NO];
  1000. }
  1001. - (void)moveRightAndModifySelection:(id)sender
  1002. {
  1003. [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + 1 direction:1 expand:YES];
  1004. }
  1005. - (void)moveDown:(id)sender
  1006. {
  1007. [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + [self numberOfColumns] direction:1 expand:NO];
  1008. }
  1009. - (void)moveDownAndModifySelection:(id)sender
  1010. {
  1011. [self _modifySelectionWithNewIndex:[[self selectionIndexes] lastIndex] + [self numberOfColumns] direction:1 expand:YES];
  1012. }
  1013. - (void)moveUp:(id)sender
  1014. {
  1015. var index = [[self selectionIndexes] firstIndex];
  1016. if (index == CPNotFound)
  1017. index = [[self items] count];
  1018. [self _modifySelectionWithNewIndex:index - [self numberOfColumns] direction:-1 expand:NO];
  1019. }
  1020. - (void)moveUpAndModifySelection:(id)sender
  1021. {
  1022. var index = [[self selectionIndexes] firstIndex];
  1023. if (index == CPNotFound)
  1024. index = [[self items] count];
  1025. [self _modifySelectionWithNewIndex:index - [self numberOfColumns] direction:-1 expand:YES];
  1026. }
  1027. - (void)deleteBackward:(id)sender
  1028. {
  1029. if ([[self delegate] respondsToSelector:@selector(collectionView:shouldDeleteItemsAtIndexes:)])
  1030. {
  1031. [[self delegate] collectionView:self shouldDeleteItemsAtIndexes:[self selectionIndexes]];
  1032. var index = [[self selectionIndexes] firstIndex];
  1033. if (index > [[self content] count] - 1)
  1034. [self setSelectionIndexes:[CPIndexSet indexSetWithIndex:[[self content] count] - 1]];
  1035. [self _scrollToSelection];
  1036. [self setNeedsDisplay:YES];
  1037. }
  1038. }
  1039. - (void)keyDown:(CPEvent)anEvent
  1040. {
  1041. [self interpretKeyEvents:[anEvent]];
  1042. }
  1043. - (void)setAutoresizingMask:(unsigned)aMask
  1044. {
  1045. [super setAutoresizingMask:0];
  1046. }
  1047. @end
  1048. @implementation CPCollectionView (Deprecated)
  1049. - (CGRect)rectForItemAtIndex:(int)anIndex
  1050. {
  1051. _CPReportLenientDeprecation([self class], _cmd, @selector(frameForItemAtIndex:));
  1052. // Don't re-compute anything just grab the current frame
  1053. // This allows subclasses to override tile without messing this up.
  1054. return [self frameForItemAtIndex:anIndex];
  1055. }
  1056. - (CGRect)rectForItemsAtIndexes:(CPIndexSet)anIndexSet
  1057. {
  1058. _CPReportLenientDeprecation([self class], _cmd, @selector(frameForItemsAtIndexes:));
  1059. return [self frameForItemsAtIndexes:anIndexSet];
  1060. }
  1061. @end
  1062. @implementation CPCollectionView (CPCollectionViewDelegate)
  1063. /*
  1064. @ignore
  1065. Return YES if the delegate implements collectionView:validateDrop:proposedIndex:dropOperation:
  1066. */
  1067. - (BOOL)_delegateRespondsToCollectionViewValidateDropProposedIndexDropOperation
  1068. {
  1069. return _implementedDelegateMethods & CPCollectionViewDelegate_collectionView_validateDrop_proposedIndex_dropOperation_;
  1070. }
  1071. /*
  1072. @ignore
  1073. Return YES if the delegate implements collectionView:menuForItemAtIndex:
  1074. */
  1075. - (BOOL)_delegateRespondsToCollectionViewMenuForItemAtIndex
  1076. {
  1077. return _implementedDelegateMethods & CPCollectionViewDelegate_collectionView_menuForItemAtIndex_;
  1078. }
  1079. /*
  1080. @ignore
  1081. Return YES if the delegate implements collectionView:dragTypesForItemsAtIndexes:
  1082. */
  1083. - (BOOL)_delegateRespondsToCollectionViewDragTypesForItemsAtIndexes
  1084. {
  1085. return _implementedDelegateMethods & CPCollectionViewDelegate_collectionView_dragTypesForItemsAtIndexes_;
  1086. }
  1087. /*!
  1088. @ignore
  1089. Call delegate collectionView:acceptDrop:index:dropOperation:
  1090. */
  1091. - (BOOL)_sendDelegateAcceptDrop:(id)draggingInfo index:(CPInteger)index dropOperation:(CPCollectionViewDropOperation)dropOperation
  1092. {
  1093. if (!(_implementedDelegateMethods & CPCollectionViewDelegate_collectionView_acceptDrop_index_dropOperation_))
  1094. return NO;
  1095. return [_delegate collectionView:self acceptDrop:draggingInfo index:index dropOperation:dropOperation];
  1096. }
  1097. /*!
  1098. @ignore
  1099. Call delegate collectionView:canDragItemsAtIndexes:withEvent:
  1100. */
  1101. - (BOOL)_sendDelegateCanDragItemsAtIndexes:(CPIndexSet)indexes withEvent:(CPEvent)anEvent
  1102. {
  1103. if (!(_implementedDelegateMethods & CPCollectionViewDelegate_collectionView_canDragItemsAtIndexes_withEvent_))
  1104. return YES;
  1105. return [_delegate collectionView:self canDragItemsAtIndexes:indexes withEvent:anEvent];
  1106. }
  1107. /*!
  1108. @ignore
  1109. Call delegate collectionView:writeItemsAtIndexes:toPasteboard:
  1110. */
  1111. - (BOOL)_sendDelegateWriteItemsAtIndexes:(CPIndexSet)indexes toPasteboard:(CPPasteboard)pasteboard
  1112. {
  1113. if (!(_implementedDelegateMethods & CPCollectionViewDelegate_collectionView_writeItemsAtIndexes_toPasteboard_))
  1114. return NO;
  1115. return [_delegate collectionView:self writeItemsAtIndexes:indexes toPasteboard:pasteboard];
  1116. }
  1117. /*!
  1118. @ignore
  1119. Call delegate collectionView:dragTypesForItemsAtIndexes:
  1120. */
  1121. - (CPArray)_sendDelegateDragTypesForItemsAtIndexes:(CPIndexSet)indexes
  1122. {
  1123. if (!(_implementedDelegateMethods & CPCollectionViewDelegate_collectionView_dragTypesForItemsAtIndexes_))
  1124. return [];
  1125. return [_delegate collectionView:self dragTypesForItemsAtIndexes:indexes];
  1126. }
  1127. /*!
  1128. @ignore
  1129. Call delegate collectionView:dataForItemsAtIndexes:forType:
  1130. */
  1131. - (CPData)_sendDelegateDataForItemsAtIndexes:(CPIndexSet)indexes forType:(CPString)aType
  1132. {
  1133. if (!(_implementedDelegateMethods & CPCollectionViewDelegate_collectionView_dataForItemsAtIndexes_forType_))
  1134. return nil;
  1135. return [_delegate collectionView:self dataForItemsAtIndexes:indexes forType:aType];
  1136. }
  1137. /*!
  1138. @ignore
  1139. Call delegate collectionView:validateDrop:proposedIndex:dropOperation:
  1140. */
  1141. - (CPDragOperation)_sendDelegateValidateDrop:(id)draggingInfo proposedIndex:(CPInteger)proposedDropIndex dropOperation:(CPCollectionViewDropOperation)proposedDropOperation
  1142. {
  1143. if (!(_implementedDelegateMethods & CPCollectionViewDelegate_collectionView_validateDrop_proposedIndex_dropOperation_))
  1144. return CPDragOperationNone;
  1145. return [_delegate collectionView:self validateDrop:draggingInfo proposedIndex:proposedDropIndex dropOperation:proposedDropOperation];
  1146. }
  1147. /*!
  1148. @ignore
  1149. Call delegate collectionView:didDoubleClickOnItemAtIndex:
  1150. */
  1151. - (void)_sendDelegateDidDoubleClickOnItemAtIndex:(int)index
  1152. {
  1153. if (!(_implementedDelegateMethods & CPCollectionViewDelegate_collectionView_didDoubleClickOnItemAtIndex_))
  1154. return;
  1155. return [_delegate collectionView:self didDoubleClickOnItemAtIndex:index];
  1156. }
  1157. /*!
  1158. @ignore
  1159. Call delegate collectionView:menuForItemAtIndex:
  1160. */
  1161. - (void)_sendDelegateMenuForItemAtIndex:(CPInteger)anIndex
  1162. {
  1163. if (!(_implementedDelegateMethods & CPCollectionViewDelegate_collectionView_menuForItemAtIndex_))
  1164. return nil;
  1165. return [_delegate collectionView:self menuForItemAtIndex:anIndex];
  1166. }
  1167. /*!
  1168. @ignore
  1169. Call delegate draggingViewForItemsAtIndexes:withEvent:offset:
  1170. */
  1171. - (CPView)_sendDelegateDraggingViewForItemsAtIndexes:(CPIndexSet)indexes withEvent:(CPEvent)anEvent offset:(CGPoint)dragImageOffset
  1172. {
  1173. if (!(_implementedDelegateMethods & CPCollectionViewDelegate_collectionView_draggingViewForItemsAtIndexes_withEvent_offset))
  1174. return [self draggingViewForItemsAtIndexes:indexes withEvent:anEvent offset:dragImageOffset];
  1175. return [_delegate collectionView:self draggingViewForItemsAtIndexes:indexes withEvent:anEvent offset:dragImageOffset];
  1176. }
  1177. @end
  1178. var CPCollectionViewMinItemSizeKey = @"CPCollectionViewMinItemSizeKey",
  1179. CPCollectionViewMaxItemSizeKey = @"CPCollectionViewMaxItemSizeKey",
  1180. CPCollectionViewVerticalMarginKey = @"CPCollectionViewVerticalMarginKey",
  1181. CPCollectionViewMaxNumberOfRowsKey = @"CPCollectionViewMaxNumberOfRowsKey",
  1182. CPCollectionViewMaxNumberOfColumnsKey = @"CPCollectionViewMaxNumberOfColumnsKey",
  1183. CPCollectionViewSelectableKey = @"CPCollectionViewSelectableKey",
  1184. CPCollectionViewAllowsMultipleSelectionKey = @"CPCollectionViewAllowsMultipleSelectionKey",
  1185. CPCollectionViewBackgroundColorsKey = @"CPCollectionViewBackgroundColorsKey";
  1186. @implementation CPCollectionView (CPCoding)
  1187. - (id)initWithCoder:(CPCoder)aCoder
  1188. {
  1189. self = [super initWithCoder:aCoder];
  1190. if (self)
  1191. {
  1192. _minItemSize = [aCoder decodeSizeForKey:CPCollectionViewMinItemSizeKey];
  1193. _maxItemSize = [aCoder decodeSizeForKey:CPCollectionViewMaxItemSizeKey];
  1194. _maxNumberOfRows = [aCoder decodeIntForKey:CPCollectionViewMaxNumberOfRowsKey];
  1195. _maxNumberOfColumns = [aCoder decodeIntForKey:CPCollectionViewMaxNumberOfColumnsKey];
  1196. _verticalMargin = [aCoder decodeFloatForKey:CPCollectionViewVerticalMarginKey];
  1197. _isSelectable = [aCoder decodeBoolForKey:CPCollectionViewSelectableKey];
  1198. _allowsMultipleSelection = [aCoder decodeBoolForKey:CPCollectionViewAllowsMultipleSelectionKey];
  1199. [self setBackgroundColors:[aCoder decodeObjectForKey:CPCollectionViewBackgroundColorsKey]];
  1200. [self _init];
  1201. }
  1202. return self;
  1203. }
  1204. - (void)encodeWithCoder:(CPCoder)aCoder
  1205. {
  1206. [super encodeWithCoder:aCoder];
  1207. if (!CGSizeEqualToSize(_minItemSize, CGSizeMakeZero()))
  1208. [aCoder encodeSize:_minItemSize forKey:CPCollectionViewMinItemSizeKey];
  1209. if (!CGSizeEqualToSize(_maxItemSize, CGSizeMakeZero()))
  1210. [aCoder encodeSize:_maxItemSize forKey:CPCollectionViewMaxItemSizeKey];
  1211. [aCoder encodeInt:_maxNumberOfRows forKey:CPCollectionViewMaxNumberOfRowsKey];
  1212. [aCoder encodeInt:_maxNumberOfColumns forKey:CPCollectionViewMaxNumberOfColumnsKey];
  1213. [aCoder encodeBool:_isSelectable forKey:CPCollectionViewSelectableKey];
  1214. [aCoder encodeBool:_allowsMultipleSelection forKey:CPCollectionViewAllowsMultipleSelectionKey];
  1215. [aCoder encodeFloat:_verticalMargin forKey:CPCollectionViewVerticalMarginKey];
  1216. [aCoder encodeObject:_backgroundColors forKey:CPCollectionViewBackgroundColorsKey];
  1217. }
  1218. @end