PageRenderTime 46ms CodeModel.GetById 18ms app.highlight 14ms RepoModel.GetById 1ms app.codeStats 0ms

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