1/** <title>NSCollectionView</title> 2 3 Copyright (C) 2013 Free Software Foundation, Inc. 4 5 Author: Doug Simons (doug.simons@testplant.com) 6 Frank LeGrand (frank.legrand@testplant.com) 7 Date: February 2013 8 9 This file is part of the GNUstep GUI Library. 10 11 This library is free software; you can redistribute it and/or 12 modify it under the terms of the GNU Lesser General Public 13 License as published by the Free Software Foundation; either 14 version 2 of the License, or (at your option) any later version. 15 16 This library is distributed in the hope that it will be useful, 17 but WITHOUT ANY WARRANTY; without even the implied warranty of 18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 Lesser General Public License for more details. 20 21 You should have received a copy of the GNU Lesser General Public 22 License along with this library; see the file COPYING.LIB. 23 If not, see <http://www.gnu.org/licenses/> or write to the 24 Free Software Foundation, 51 Franklin Street, Fifth Floor, 25 Boston, MA 02110-1301, USA. 26*/ 27 28#import "Foundation/NSKeyedArchiver.h" 29#import <Foundation/NSGeometry.h> 30#import <Foundation/NSIndexSet.h> 31#import <Foundation/NSKeyedArchiver.h> 32 33#import "AppKit/NSApplication.h" 34#import "AppKit/NSClipView.h" 35#import "AppKit/NSCollectionView.h" 36#import "AppKit/NSCollectionViewItem.h" 37#import "AppKit/NSEvent.h" 38#import "AppKit/NSGraphics.h" 39#import "AppKit/NSImage.h" 40#import "AppKit/NSKeyValueBinding.h" 41#import "AppKit/NSPasteboard.h" 42#import "AppKit/NSWindow.h" 43 44#include <math.h> 45 46static NSString* NSCollectionViewMinItemSizeKey = @"NSMinGridSize"; 47static NSString* NSCollectionViewMaxItemSizeKey = @"NSMaxGridSize"; 48//static NSString* NSCollectionViewVerticalMarginKey = @"NSCollectionViewVerticalMarginKey"; 49static NSString* NSCollectionViewMaxNumberOfRowsKey = @"NSMaxNumberOfGridRows"; 50static NSString* NSCollectionViewMaxNumberOfColumnsKey = @"NSMaxNumberOfGridColumns"; 51static NSString* NSCollectionViewSelectableKey = @"NSSelectable"; 52static NSString* NSCollectionViewAllowsMultipleSelectionKey = @"NSAllowsMultipleSelection"; 53static NSString* NSCollectionViewBackgroundColorsKey = @"NSBackgroundColors"; 54 55/* 56 * Class variables 57 */ 58static NSString *placeholderItem = nil; 59 60@interface NSCollectionView (CollectionViewInternalPrivate) 61 62- (void) _initDefaults; 63- (void) _resetItemSize; 64- (void) _removeItemsViews; 65- (NSInteger) _indexAtPoint: (NSPoint)point; 66 67- (NSRect) _frameForRowOfItemAtIndex: (NSUInteger)theIndex; 68- (NSRect) _frameForRowsAroundItemAtIndex: (NSUInteger)theIndex; 69 70- (void) _modifySelectionWithNewIndex: (NSUInteger)anIndex 71 direction: (int)aDirection 72 expand: (BOOL)shouldExpand; 73 74- (void) _moveDownAndExpandSelection: (BOOL)shouldExpand; 75- (void) _moveUpAndExpandSelection: (BOOL)shouldExpand; 76- (void) _moveLeftAndExpandSelection: (BOOL)shouldExpand; 77- (void) _moveRightAndExpandSelection: (BOOL)shouldExpand; 78 79- (BOOL) _writeItemsAtIndexes: (NSIndexSet *)indexes 80 toPasteboard: (NSPasteboard *)pasteboard; 81 82- (BOOL) _startDragOperationWithEvent: (NSEvent*)event 83 clickedIndex: (NSUInteger)index; 84 85- (void) _selectWithEvent: (NSEvent *)theEvent 86 index: (NSUInteger)index; 87 88@end 89 90 91@implementation NSCollectionView 92 93// 94// Class methods 95// 96+ (void) initialize 97{ 98 if (self == [NSCollectionView class]) 99 { 100 placeholderItem = @"Placeholder"; 101 [self exposeBinding: NSContentBinding]; 102 } 103} 104 105- (id) initWithFrame: (NSRect)frame 106{ 107 if ((self = [super initWithFrame:frame])) 108 { 109 [self _initDefaults]; 110 } 111 return self; 112} 113 114-(void) _initDefaults 115{ 116// _draggingSourceOperationMaskForLocal = NSDragOperationCopy | NSDragOperationLink | NSDragOperationGeneric | NSDragOperationPrivate; 117 _draggingSourceOperationMaskForLocal = NSDragOperationGeneric | NSDragOperationMove | NSDragOperationCopy; 118 _draggingSourceOperationMaskForRemote = NSDragOperationGeneric | NSDragOperationMove | NSDragOperationCopy; 119 [self _resetItemSize]; 120 _content = [[NSArray alloc] init]; 121 _items = [[NSMutableArray alloc] init]; 122 _selectionIndexes = [[NSIndexSet alloc] init]; 123 _draggingOnIndex = NSNotFound; 124} 125 126- (void) _resetItemSize 127{ 128 if (itemPrototype && ([itemPrototype view] != nil)) 129 { 130 _itemSize = [[itemPrototype view] frame].size; 131 _minItemSize = NSMakeSize (_itemSize.width, _itemSize.height); 132 _maxItemSize = NSMakeSize (_itemSize.width, _itemSize.height); 133 } 134 else 135 { 136 // FIXME: This is just arbitrary. 137 // What are we suppose to do when we don't have a prototype? 138 _itemSize = NSMakeSize(120.0, 100.0); 139 _minItemSize = NSMakeSize(120.0, 100.0); 140 _maxItemSize = NSMakeSize(120.0, 100.0); 141 } 142} 143 144- (void) drawRect: (NSRect)dirtyRect 145{ 146 // TODO: Implement "use Alternating Colors" 147 if (_backgroundColors && [_backgroundColors count] > 0) 148 { 149 NSColor *bgColor = [_backgroundColors objectAtIndex: 0]; 150 [bgColor set]; 151 NSRectFill(dirtyRect); 152 } 153 154 NSPoint origin = dirtyRect.origin; 155 NSSize size = dirtyRect.size; 156 NSPoint oppositeOrigin = NSMakePoint (origin.x + size.width, origin.y + size.height); 157 158 NSInteger firstIndexInRect = MAX(0, [self _indexAtPoint: origin]); 159 // I had to extract these values from the macro to get it 160 // working correctly. 161 NSInteger index = [self _indexAtPoint: oppositeOrigin]; 162 NSInteger last = [_items count] - 1; 163 NSInteger lastIndexInRect = MIN(last, index); 164 165 for (index = firstIndexInRect; index <= lastIndexInRect; index++) 166 { 167 // Calling itemAtIndex: will eventually instantiate the collection view item, 168 // if it hasn't been done already. 169 NSCollectionViewItem *collectionItem = [self itemAtIndex: index]; 170 NSView *view = [collectionItem view]; 171 [view setFrame: [self frameForItemAtIndex: index]]; 172 } 173} 174 175- (void) dealloc 176{ 177 //[[NSNotificationCenter defaultCenter] removeObserver: self]; 178 179 DESTROY (_content); 180 181 // FIXME: Not clear if we should destroy the top-level item "itemPrototype" loaded in the nib file. 182 DESTROY (itemPrototype); 183 184 DESTROY (_backgroundColors); 185 DESTROY (_selectionIndexes); 186 DESTROY (_items); 187 //DESTROY (_mouseDownEvent); 188 [super dealloc]; 189} 190 191- (BOOL) isFlipped 192{ 193 return YES; 194} 195 196- (BOOL) allowsMultipleSelection 197{ 198 return _allowsMultipleSelection; 199} 200 201- (void) setAllowsMultipleSelection: (BOOL)flag 202{ 203 _allowsMultipleSelection = flag; 204} 205 206- (NSArray *) backgroundColors 207{ 208 return _backgroundColors; 209} 210 211- (void) setBackgroundColors: (NSArray *)colors 212{ 213 _backgroundColors = [colors copy]; 214 [self setNeedsDisplay: YES]; 215} 216 217- (NSArray *) content 218{ 219 return _content; 220} 221 222- (void) setContent: (NSArray *)content 223{ 224 NSInteger i; 225 226 ASSIGN(_content, content); 227 [self _removeItemsViews]; 228 229 RELEASE (_items); 230 _items = [[NSMutableArray alloc] initWithCapacity: [_content count]]; 231 232 for (i = 0; i < [_content count]; i++) 233 { 234 [_items addObject: placeholderItem]; 235 } 236 237 if (!itemPrototype) 238 { 239 return; 240 } 241 else 242 { 243 [self _resetItemSize]; 244 // Force recalculation of each item's frame 245 _itemSize = _minItemSize; 246 _tileWidth = -1.0; 247 [self tile]; 248 } 249} 250 251- (id < NSCollectionViewDelegate >) delegate 252{ 253 return delegate; 254} 255 256- (void) setDelegate: (id < NSCollectionViewDelegate >)aDelegate 257{ 258 delegate = aDelegate; 259} 260 261- (NSCollectionViewItem *) itemPrototype 262{ 263 return itemPrototype; 264} 265 266- (void) setItemPrototype: (NSCollectionViewItem *)prototype 267{ 268 ASSIGN(itemPrototype, prototype); 269 [self _resetItemSize]; 270} 271 272- (CGFloat) verticalMargin 273{ 274 return _verticalMargin; 275} 276 277- (void) setVerticalMargin: (CGFloat)margin 278{ 279 if (_verticalMargin == margin) 280 return; 281 282 _verticalMargin = margin; 283 [self tile]; 284} 285 286- (NSSize) maxItemSize 287{ 288 return _maxItemSize; 289} 290 291- (void) setMaxItemSize: (NSSize)size 292{ 293 if (NSEqualSizes(_maxItemSize, size)) 294 return; 295 296 _maxItemSize = size; 297 [self tile]; 298} 299 300- (NSUInteger) maxNumberOfColumns 301{ 302 return _maxNumberOfColumns; 303} 304 305- (void) setMaxNumberOfColumns: (NSUInteger)number 306{ 307 _maxNumberOfColumns = number; 308} 309 310- (NSUInteger) maxNumberOfRows 311{ 312 return _maxNumberOfRows; 313} 314 315- (void) setMaxNumberOfRows: (NSUInteger)number 316{ 317 _maxNumberOfRows = number; 318} 319 320- (NSSize) minItemSize 321{ 322 return _minItemSize; 323} 324 325- (void) setMinItemSize: (NSSize)size 326{ 327 if (NSEqualSizes(_minItemSize, size)) 328 return; 329 330 _minItemSize = size; 331 [self tile]; 332} 333 334- (BOOL) isSelectable 335{ 336 return _isSelectable; 337} 338 339- (void) setSelectable: (BOOL)flag 340{ 341 _isSelectable = flag; 342 if (!_isSelectable) 343 { 344 NSInteger index = -1; 345 while ((index = [_selectionIndexes indexGreaterThanIndex: index]) != NSNotFound) 346 { 347 id item = [_items objectAtIndex: index]; 348 if ([item respondsToSelector: @selector(setSelected:)]) 349 { 350 [item setSelected:NO]; 351 } 352 } 353 } 354} 355 356- (NSIndexSet *) selectionIndexes 357{ 358 return _selectionIndexes; 359} 360 361- (void) setSelectionIndexes: (NSIndexSet *)indexes 362{ 363 if (!_isSelectable) 364 { 365 return; 366 } 367 368 if (![_selectionIndexes isEqual: indexes]) 369 { 370 ASSIGN(_selectionIndexes, indexes); 371 } 372 373 NSUInteger index = 0; 374 while (index < [_items count]) 375 { 376 id item = [_items objectAtIndex: index]; 377 if ([item respondsToSelector: @selector(setSelected:)]) 378 { 379 [item setSelected:NO]; 380 } 381 index++; 382 } 383 384 index = -1; 385 while ((index = [_selectionIndexes indexGreaterThanIndex: index]) != 386 NSNotFound) 387 { 388 id item = [_items objectAtIndex: index]; 389 if ([item respondsToSelector: @selector(setSelected:)]) 390 { 391 [item setSelected: YES]; 392 } 393 } 394} 395 396- (NSRect) frameForItemAtIndex: (NSUInteger)theIndex 397{ 398 NSRect itemFrame = NSMakeRect (0,0,0,0); 399 NSInteger index; 400 NSUInteger count = [_items count]; 401 CGFloat x = _horizontalMargin; 402 CGFloat y = -_itemSize.height; 403 404 if (_maxNumberOfColumns > 0 && _maxNumberOfRows > 0) 405 { 406 count = MIN(count, _maxNumberOfColumns * _maxNumberOfRows); 407 } 408 409 for (index = 0; index < count; ++index) 410 { 411 if (index % _numberOfColumns == 0) 412 { 413 x = _horizontalMargin; 414 y += _verticalMargin + _itemSize.height; 415 } 416 417 if (index == theIndex) 418 { 419 NSInteger draggingOffset = 0; 420 421 if (_draggingOnIndex != NSNotFound) 422 { 423 NSInteger draggingOnRow = (_draggingOnIndex / _numberOfColumns); 424 NSInteger currentIndexRow = (theIndex / _numberOfColumns); 425 426 if (draggingOnRow == currentIndexRow) 427 { 428 if (index < _draggingOnIndex) 429 { 430 draggingOffset = -20; 431 } 432 else 433 { 434 draggingOffset = 20; 435 } 436 } 437 } 438 itemFrame = NSMakeRect ((x + draggingOffset), y, _itemSize.width, _itemSize.height); 439 break; 440 } 441 442 x += _itemSize.width + _horizontalMargin; 443 } 444 return itemFrame; 445} 446 447- (NSRect) _frameForRowOfItemAtIndex: (NSUInteger)theIndex 448{ 449 NSRect itemFrame = [self frameForItemAtIndex: theIndex]; 450 451 return NSMakeRect (0, itemFrame.origin.y, [self bounds].size.width, itemFrame.size.height); 452} 453 454// Returns the frame of an item's row with the row above and the row below 455- (NSRect) _frameForRowsAroundItemAtIndex: (NSUInteger)theIndex 456{ 457 NSRect itemRowFrame = [self _frameForRowOfItemAtIndex: theIndex]; 458 CGFloat y = MAX (0, itemRowFrame.origin.y - itemRowFrame.size.height); 459 CGFloat height = MIN (itemRowFrame.size.height * 3, [self bounds].size.height); 460 461 return NSMakeRect(0, y, itemRowFrame.size.width, height); 462} 463 464- (NSCollectionViewItem *) itemAtIndex: (NSUInteger)index 465{ 466 id item = [_items objectAtIndex: index]; 467 468 if (item == placeholderItem) 469 { 470 item = [self newItemForRepresentedObject: [_content objectAtIndex: index]]; 471 [_items replaceObjectAtIndex: index withObject: item]; 472 if ([[self selectionIndexes] containsIndex: index]) 473 { 474 [item setSelected: YES]; 475 } 476 [self addSubview: [item view]]; 477 RELEASE(item); 478 } 479 return item; 480} 481 482- (NSCollectionViewItem *) newItemForRepresentedObject: (id)object 483{ 484 NSCollectionViewItem *collectionItem = nil; 485 if (itemPrototype) 486 { 487 collectionItem = [itemPrototype copy]; 488 [collectionItem setRepresentedObject: object]; 489 } 490 return collectionItem; 491} 492 493- (void) _removeItemsViews 494{ 495 if (!_items) 496 return; 497 498 NSUInteger count = [_items count]; 499 500 while (count--) 501 { 502 id item = [_items objectAtIndex: count]; 503 504 if ([item respondsToSelector: @selector(view)]) 505 { 506 [[item view] removeFromSuperview]; 507 [item setSelected: NO]; 508 } 509 } 510} 511 512- (void) tile 513{ 514 // TODO: - Animate items, Add Fade-in/Fade-out (as in Cocoa) 515 // - Put the tiling on a delay 516 if (!_items) 517 return; 518 519 CGFloat width = [self bounds].size.width; 520 521 if (width == _tileWidth) 522 return; 523 524 NSSize itemSize = NSMakeSize(_minItemSize.width, _minItemSize.height); 525 526 _numberOfColumns = MAX(1.0, floor(width / itemSize.width)); 527 528 if (_maxNumberOfColumns > 0) 529 { 530 _numberOfColumns = MIN(_maxNumberOfColumns, _numberOfColumns); 531 } 532 533 if (_numberOfColumns == 0) 534 { 535 _numberOfColumns = 1; 536 } 537 538 CGFloat remaining = width - _numberOfColumns * itemSize.width; 539 540 if (remaining > 0 && itemSize.width < _maxItemSize.width) 541 { 542 itemSize.width = MIN(_maxItemSize.width, itemSize.width + 543 floor(remaining / _numberOfColumns)); 544 } 545 546 if (_maxNumberOfColumns == 1 && itemSize.width < 547 _maxItemSize.width && itemSize.width < width) 548 { 549 itemSize.width = MIN(_maxItemSize.width, width); 550 } 551 552 if (!NSEqualSizes(_itemSize, itemSize)) 553 { 554 _itemSize = itemSize; 555 } 556 557 NSInteger index; 558 NSUInteger count = [_items count]; 559 560 if (_maxNumberOfColumns > 0 && _maxNumberOfRows > 0) 561 { 562 count = MIN(count, _maxNumberOfColumns * _maxNumberOfRows); 563 } 564 565 _horizontalMargin = floor((width - _numberOfColumns * itemSize.width) / 566 (_numberOfColumns + 1)); 567 CGFloat y = -itemSize.height; 568 569 for (index = 0; index < count; ++index) 570 { 571 if (index % _numberOfColumns == 0) 572 { 573 y += _verticalMargin + itemSize.height; 574 } 575 } 576 577 id superview = [self superview]; 578 CGFloat proposedHeight = y + itemSize.height + _verticalMargin; 579 if ([superview isKindOfClass: [NSClipView class]]) 580 { 581 NSSize superviewSize = [superview bounds].size; 582 proposedHeight = MAX(superviewSize.height, proposedHeight); 583 } 584 585 _tileWidth = width; 586 [self setFrameSize: NSMakeSize(width, proposedHeight)]; 587 [self setNeedsDisplay: YES]; 588} 589 590- (void) resizeSubviewsWithOldSize: (NSSize)aSize 591{ 592 NSSize currentSize = [self frame].size; 593 if (!NSEqualSizes(currentSize, aSize)) 594 { 595 [self tile]; 596 } 597} 598 599- (id) initWithCoder: (NSCoder *)aCoder 600{ 601 self = [super initWithCoder:aCoder]; 602 603 if (self) 604 { 605 if ([aCoder allowsKeyedCoding]) 606 { 607 _itemSize = NSMakeSize(0, 0); 608 _tileWidth = -1.0; 609 610 _minItemSize = [aCoder decodeSizeForKey: NSCollectionViewMinItemSizeKey]; 611 _maxItemSize = [aCoder decodeSizeForKey: NSCollectionViewMaxItemSizeKey]; 612 613 _maxNumberOfRows = [aCoder decodeInt64ForKey: NSCollectionViewMaxNumberOfRowsKey]; 614 _maxNumberOfColumns = [aCoder decodeInt64ForKey: NSCollectionViewMaxNumberOfColumnsKey]; 615 616 //_verticalMargin = [aCoder decodeFloatForKey: NSCollectionViewVerticalMarginKey]; 617 618 _isSelectable = [aCoder decodeBoolForKey: NSCollectionViewSelectableKey]; 619 _allowsMultipleSelection = [aCoder decodeBoolForKey: NSCollectionViewAllowsMultipleSelectionKey]; 620 621 [self setBackgroundColors: [aCoder decodeObjectForKey: NSCollectionViewBackgroundColorsKey]]; 622 } 623 else 624 { 625 } 626 } 627 [self _initDefaults]; 628 629 return self; 630} 631 632- (void) encodeWithCoder: (NSCoder *)aCoder 633{ 634 [super encodeWithCoder: aCoder]; 635 if ([aCoder allowsKeyedCoding]) 636 { 637 if (!NSEqualSizes(_minItemSize, NSMakeSize(0, 0))) 638 { 639 [aCoder encodeSize: _minItemSize forKey: NSCollectionViewMinItemSizeKey]; 640 } 641 642 if (!NSEqualSizes(_maxItemSize, NSMakeSize(0, 0))) 643 { 644 [aCoder encodeSize: _maxItemSize forKey: NSCollectionViewMaxItemSizeKey]; 645 } 646 647 [aCoder encodeInt64: _maxNumberOfRows 648 forKey: NSCollectionViewMaxNumberOfRowsKey]; 649 [aCoder encodeInt64: _maxNumberOfColumns 650 forKey: NSCollectionViewMaxNumberOfColumnsKey]; 651 652 [aCoder encodeBool: _isSelectable 653 forKey: NSCollectionViewSelectableKey]; 654 [aCoder encodeBool: _allowsMultipleSelection 655 forKey: NSCollectionViewAllowsMultipleSelectionKey]; 656 657 //[aCoder encodeCGFloat: _verticalMargin forKey: NSCollectionViewVerticalMarginKey]; 658 [aCoder encodeObject: _backgroundColors 659 forKey: NSCollectionViewBackgroundColorsKey]; 660 } 661 else 662 { 663 } 664} 665 666- (void) mouseDown: (NSEvent *)theEvent 667{ 668 NSPoint initialLocation = [theEvent locationInWindow]; 669 NSPoint location = [self convertPoint: initialLocation fromView: nil]; 670 NSInteger index = [self _indexAtPoint: location]; 671 NSEvent *lastEvent = theEvent; 672 BOOL done = NO; 673 NSUInteger eventMask = (NSLeftMouseUpMask 674 | NSLeftMouseDownMask 675 | NSLeftMouseDraggedMask 676 | NSPeriodicMask); 677 NSDate *distantFuture = [NSDate distantFuture]; 678 679 while (!done) 680 { 681 lastEvent = [NSApp nextEventMatchingMask: eventMask 682 untilDate: distantFuture 683 inMode: NSEventTrackingRunLoopMode 684 dequeue: YES]; 685 NSEventType eventType = [lastEvent type]; 686 NSPoint mouseLocationWin = [lastEvent locationInWindow]; 687 switch (eventType) 688 { 689 case NSLeftMouseDown: 690 break; 691 case NSLeftMouseDragged: 692 if (fabs(mouseLocationWin.x - initialLocation.x) >= 2 693 || fabs(mouseLocationWin.y - initialLocation.y) >= 2) 694 { 695 if ([self _startDragOperationWithEvent: theEvent clickedIndex: index]) 696 { 697 done = YES; 698 } 699 } 700 break; 701 case NSLeftMouseUp: 702 [self _selectWithEvent: theEvent index: index]; 703 done = YES; 704 break; 705 default: 706 done = NO; 707 break; 708 } 709 } 710} 711 712- (void) _selectWithEvent: (NSEvent *)theEvent index: (NSUInteger)index 713{ 714 NSMutableIndexSet *currentIndexSet = [[NSMutableIndexSet alloc] initWithIndexSet: [self selectionIndexes]]; 715 716 if (_isSelectable && (index < [_items count])) 717 { 718 if (_allowsMultipleSelection 719 && (([theEvent modifierFlags] & NSControlKeyMask) 720 || ([theEvent modifierFlags] & NSShiftKeyMask))) 721 { 722 if ([theEvent modifierFlags] & NSControlKeyMask) 723 { 724 if ([currentIndexSet containsIndex: index]) 725 { 726 [currentIndexSet removeIndex: index]; 727 } 728 else 729 { 730 [currentIndexSet addIndex: index]; 731 } 732 [self setSelectionIndexes: currentIndexSet]; 733 } 734 else if ([theEvent modifierFlags] & NSShiftKeyMask) 735 { 736 NSUInteger firstSelectedIndex = [currentIndexSet firstIndex]; 737 NSRange selectedRange; 738 739 if (firstSelectedIndex == NSNotFound) 740 { 741 selectedRange = NSMakeRange(index, index); 742 } 743 else if (index < firstSelectedIndex) 744 { 745 selectedRange = NSMakeRange(index, (firstSelectedIndex - index + 1)); 746 } 747 else 748 { 749 selectedRange = NSMakeRange(firstSelectedIndex, (index - firstSelectedIndex + 1)); 750 } 751 [currentIndexSet addIndexesInRange: selectedRange]; 752 [self setSelectionIndexes: currentIndexSet]; 753 } 754 } 755 else 756 { 757 [self setSelectionIndexes: [NSIndexSet indexSetWithIndex: index]]; 758 } 759 [[self window] makeFirstResponder: self]; 760 } 761 else 762 { 763 [self setSelectionIndexes: [NSIndexSet indexSet]]; 764 } 765 RELEASE (currentIndexSet); 766} 767 768- (NSInteger) _indexAtPoint: (NSPoint)point 769{ 770 NSInteger row = floor(point.y / (_itemSize.height + _verticalMargin)); 771 NSInteger column = floor(point.x / (_itemSize.width + _horizontalMargin)); 772 return (column + (row * _numberOfColumns)); 773} 774 775- (BOOL) acceptsFirstResponder 776{ 777 return YES; 778} 779 780/* MARK: Keyboard Interaction */ 781 782- (void) keyDown: (NSEvent *)theEvent 783{ 784 [self interpretKeyEvents: [NSArray arrayWithObject: theEvent]]; 785} 786 787-(void) moveUp: (id)sender 788{ 789 [self _moveUpAndExpandSelection: NO]; 790} 791 792-(void) moveUpAndModifySelection: (id)sender 793{ 794 [self _moveUpAndExpandSelection: YES]; 795} 796 797- (void) _moveUpAndExpandSelection: (BOOL)shouldExpand 798{ 799 NSInteger index = [[self selectionIndexes] firstIndex]; 800 if (index != NSNotFound && index >= _numberOfColumns) 801 { 802 [self _modifySelectionWithNewIndex: index - _numberOfColumns 803 direction: -1 804 expand: shouldExpand]; 805 } 806} 807 808-(void) moveDown: (id)sender 809{ 810 [self _moveDownAndExpandSelection: NO]; 811} 812 813-(void) moveDownAndModifySelection: (id)sender 814{ 815 [self _moveDownAndExpandSelection: YES]; 816} 817 818-(void) _moveDownAndExpandSelection: (BOOL)shouldExpand 819{ 820 NSInteger index = [[self selectionIndexes] lastIndex]; 821 if (index != NSNotFound && (index + _numberOfColumns) < [_items count]) 822 { 823 [self _modifySelectionWithNewIndex: index + _numberOfColumns 824 direction: 1 825 expand: shouldExpand]; 826 } 827} 828 829-(void) moveLeft: (id)sender 830{ 831 [self _moveLeftAndExpandSelection: NO]; 832} 833 834-(void) moveLeftAndModifySelection: (id)sender 835{ 836 [self _moveLeftAndExpandSelection: YES]; 837} 838 839-(void) moveBackwardAndModifySelection: (id)sender 840{ 841 [self _moveLeftAndExpandSelection: YES]; 842} 843 844-(void) _moveLeftAndExpandSelection: (BOOL)shouldExpand 845{ 846 NSUInteger index = [[self selectionIndexes] firstIndex]; 847 if (index != NSNotFound && index != 0) 848 { 849 [self _modifySelectionWithNewIndex: index - 1 direction: -1 expand: shouldExpand]; 850 } 851} 852 853-(void) moveRight: (id)sender 854{ 855 [self _moveRightAndExpandSelection: NO]; 856} 857 858-(void) moveRightAndModifySelection: (id)sender 859{ 860 [self _moveRightAndExpandSelection: YES]; 861} 862 863-(void) moveForwardAndModifySelection: (id)sender 864{ 865 [self _moveRightAndExpandSelection: YES]; 866} 867 868-(void) _moveRightAndExpandSelection: (BOOL)shouldExpand 869{ 870 NSUInteger index = [[self selectionIndexes] lastIndex]; 871 if (index != NSNotFound && index != ([_items count] - 1)) 872 { 873 [self _modifySelectionWithNewIndex: index + 1 direction: 1 expand: shouldExpand]; 874 } 875} 876 877- (void) _modifySelectionWithNewIndex: (NSUInteger)anIndex 878 direction: (int)aDirection 879 expand: (BOOL)shouldExpand 880{ 881 anIndex = MIN(MAX(anIndex, 0), [_items count] - 1); 882 883 if (_allowsMultipleSelection && shouldExpand) 884 { 885 NSMutableIndexSet *newIndexSet = [[NSMutableIndexSet alloc] initWithIndexSet: _selectionIndexes]; 886 NSUInteger firstIndex = [newIndexSet firstIndex]; 887 NSUInteger lastIndex = [newIndexSet lastIndex]; 888 if (aDirection == -1) 889 { 890 [newIndexSet addIndexesInRange:NSMakeRange (anIndex, firstIndex - anIndex + 1)]; 891 } 892 else 893 { 894 [newIndexSet addIndexesInRange:NSMakeRange (lastIndex, anIndex - lastIndex + 1)]; 895 } 896 [self setSelectionIndexes: newIndexSet]; 897 RELEASE (newIndexSet); 898 } 899 else 900 { 901 [self setSelectionIndexes: [NSIndexSet indexSetWithIndex: anIndex]]; 902 } 903 904 [self scrollRectToVisible: [self frameForItemAtIndex: anIndex]]; 905} 906 907 908/* MARK: Drag & Drop */ 909 910-(NSDragOperation) draggingSourceOperationMaskForLocal: (BOOL)isLocal 911{ 912 if (isLocal) 913 { 914 return _draggingSourceOperationMaskForLocal; 915 } 916 else 917 { 918 return _draggingSourceOperationMaskForRemote; 919 } 920} 921 922-(void) setDraggingSourceOperationMask: (NSDragOperation)mask 923 forLocal: (BOOL)isLocal 924{ 925 if (isLocal) 926 { 927 _draggingSourceOperationMaskForLocal = mask; 928 } 929 else 930 { 931 _draggingSourceOperationMaskForRemote = mask; 932 } 933} 934 935- (BOOL) _startDragOperationWithEvent: (NSEvent*)event 936 clickedIndex: (NSUInteger)index 937{ 938 NSIndexSet *dragIndexes = _selectionIndexes; 939 940 if (![dragIndexes containsIndex: index] 941 && (index < [_items count])) 942 { 943 dragIndexes = [NSIndexSet indexSetWithIndex: index]; 944 } 945 946 if (![dragIndexes count]) 947 return NO; 948 949 if (![delegate respondsToSelector: @selector(collectionView:writeItemsAtIndexes:toPasteboard:)]) 950 return NO; 951 952 if ([delegate respondsToSelector: @selector(collectionView:canDragItemsAtIndexes:withEvent:)]) 953 { 954 if (![delegate collectionView: self 955 canDragItemsAtIndexes: dragIndexes 956 withEvent: event]) 957 { 958 return NO; 959 } 960 } 961 962 NSPoint downPoint = [event locationInWindow]; 963 NSPoint convertedDownPoint = [self convertPoint: downPoint fromView: nil]; 964 965 NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName: NSDragPboard]; 966 if ([self _writeItemsAtIndexes:dragIndexes toPasteboard: pasteboard]) 967 { 968 NSImage *dragImage = [self draggingImageForItemsAtIndexes: dragIndexes 969 withEvent: event 970 offset: NULL]; 971 972 [self dragImage: dragImage 973 at: convertedDownPoint 974 offset: NSMakeSize(0,0) 975 event: event 976 pasteboard: pasteboard 977 source: self 978 slideBack: YES]; 979 980 return YES; 981 } 982 return NO; 983} 984 985- (NSImage *) draggingImageForItemsAtIndexes: (NSIndexSet *)indexes 986 withEvent: (NSEvent *)event 987 offset: (NSPointPointer)dragImageOffset 988{ 989 if ([delegate respondsToSelector: @selector(collectionView:draggingImageForItemsAtIndexes:withEvent:offset:)]) 990 { 991 return [delegate collectionView: self 992 draggingImageForItemsAtIndexes: indexes 993 withEvent: event 994 offset: dragImageOffset]; 995 } 996 else 997 { 998 return [[NSImage alloc] initWithData: [self dataWithPDFInsideRect: [self bounds]]]; 999 } 1000} 1001 1002- (BOOL) _writeItemsAtIndexes: (NSIndexSet *)indexes 1003 toPasteboard: (NSPasteboard *)pasteboard 1004{ 1005 if (![delegate respondsToSelector: @selector(collectionView:writeItemsAtIndexes:toPasteboard:)]) 1006 { 1007 return NO; 1008 } 1009 else 1010 { 1011 return [delegate collectionView: self 1012 writeItemsAtIndexes: indexes 1013 toPasteboard: pasteboard]; 1014 } 1015} 1016 1017- (void) draggedImage: (NSImage *)image 1018 endedAt: (NSPoint)point 1019 operation: (NSDragOperation)operation 1020{ 1021} 1022 1023- (NSDragOperation) _draggingEnteredOrUpdated: (id<NSDraggingInfo>)sender 1024{ 1025 NSDragOperation result = NSDragOperationNone; 1026 1027 if ([delegate respondsToSelector: @selector(collectionView:validateDrop:proposedIndex:dropOperation:)]) 1028 { 1029 NSPoint location = [self convertPoint: [sender draggingLocation] fromView: nil]; 1030 NSInteger index = [self _indexAtPoint: location]; 1031 index = (index > [_items count] - 1) ? [_items count] - 1 : index; 1032 _draggingOnIndex = index; 1033 1034 NSInteger *proposedIndex = &index; 1035 NSInteger dropOperationInt = NSCollectionViewDropOn; 1036 NSCollectionViewDropOperation *dropOperation = &dropOperationInt; 1037 1038 // TODO: We currently don't do anything with the proposedIndex & dropOperation that 1039 // may get altered by the delegate. 1040 result = [delegate collectionView: self 1041 validateDrop: sender 1042 proposedIndex: proposedIndex 1043 dropOperation: dropOperation]; 1044 1045 if (result == NSDragOperationNone) 1046 { 1047 _draggingOnIndex = NSNotFound; 1048 } 1049 [self setNeedsDisplayInRect: [self _frameForRowsAroundItemAtIndex: index]]; 1050 } 1051 1052 return result; 1053} 1054 1055- (NSDragOperation) draggingEntered: (id<NSDraggingInfo>)sender 1056{ 1057 return [self _draggingEnteredOrUpdated: sender]; 1058} 1059 1060- (void) draggingExited: (id<NSDraggingInfo>)sender 1061{ 1062 [self setNeedsDisplayInRect: [self _frameForRowsAroundItemAtIndex: _draggingOnIndex]]; 1063 _draggingOnIndex = NSNotFound; 1064} 1065 1066- (NSDragOperation) draggingUpdated: (id<NSDraggingInfo>)sender 1067{ 1068 return [self _draggingEnteredOrUpdated: sender]; 1069} 1070 1071- (BOOL) prepareForDragOperation: (id<NSDraggingInfo>)sender 1072{ 1073 NSPoint location = [self convertPoint: [sender draggingLocation] fromView: nil]; 1074 NSInteger index = [self _indexAtPoint: location]; 1075 1076 _draggingOnIndex = NSNotFound; 1077 [self setNeedsDisplayInRect: [self _frameForRowsAroundItemAtIndex: index]]; 1078 return YES; 1079} 1080 1081- (BOOL) performDragOperation: (id<NSDraggingInfo>)sender 1082{ 1083 NSPoint location = [self convertPoint: [sender draggingLocation] fromView: nil]; 1084 NSInteger index = [self _indexAtPoint: location]; 1085 index = (index > [_items count] - 1) ? [_items count] - 1 : index; 1086 1087 BOOL result = NO; 1088 if ([delegate respondsToSelector: @selector(collectionView:acceptDrop:index:dropOperation:)]) 1089 { 1090 // TODO: dropOperation should be retrieved from the validateDrop delegate method. 1091 result = [delegate collectionView: self 1092 acceptDrop: sender 1093 index: index 1094 dropOperation: NSCollectionViewDropOn]; 1095 } 1096 return result; 1097} 1098 1099- (BOOL) wantsPeriodicDraggingUpdates 1100{ 1101 return YES; 1102} 1103 1104@end 1105