apps_collection_view_drag_manager.mm revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import "ui/app_list/cocoa/apps_collection_view_drag_manager.h" 6 7#include "base/logging.h" 8#include "base/mac/foundation_util.h" 9#import "ui/app_list/cocoa/apps_grid_controller.h" 10#import "ui/app_list/cocoa/apps_grid_view_item.h" 11#import "ui/app_list/cocoa/item_drag_controller.h" 12 13namespace { 14 15// Distance cursor must travel in either x or y direction to start a drag. 16const CGFloat kDragThreshold = 5; 17 18} // namespace 19 20@interface AppsCollectionViewDragManager () 21 22// Returns the item index that |theEvent| would hit in the page at |pageIndex| 23// or NSNotFound if no item is hit. 24- (size_t)itemIndexForPage:(size_t)pageIndex 25 hitWithEvent:(NSEvent*)theEvent; 26 27- (void)initiateDrag:(NSEvent*)theEvent; 28- (void)updateDrag:(NSEvent*)theEvent; 29- (void)completeDrag; 30 31- (NSMenu*)menuForEvent:(NSEvent*)theEvent 32 inPage:(NSCollectionView*)page; 33 34@end 35 36// An NSCollectionView that forwards mouse events to the factory they share. 37@interface GridCollectionView : NSCollectionView { 38 @private 39 AppsCollectionViewDragManager* factory_; 40} 41 42@property(assign, nonatomic) AppsCollectionViewDragManager* factory; 43 44@end 45 46@implementation AppsCollectionViewDragManager 47 48- (id)initWithCellSize:(NSSize)cellSize 49 rows:(size_t)rows 50 columns:(size_t)columns 51 gridController:(AppsGridController*)gridController { 52 if ((self = [super init])) { 53 cellSize_ = cellSize; 54 rows_ = rows; 55 columns_ = columns; 56 gridController_ = gridController; 57 } 58 return self; 59} 60 61- (NSCollectionView*)makePageWithFrame:(NSRect)pageFrame { 62 base::scoped_nsobject<GridCollectionView> itemCollectionView( 63 [[GridCollectionView alloc] initWithFrame:pageFrame]); 64 [itemCollectionView setFactory:self]; 65 [itemCollectionView setMaxNumberOfRows:rows_]; 66 [itemCollectionView setMinItemSize:cellSize_]; 67 [itemCollectionView setMaxItemSize:cellSize_]; 68 [itemCollectionView setSelectable:YES]; 69 [itemCollectionView setFocusRingType:NSFocusRingTypeNone]; 70 [itemCollectionView setBackgroundColors: 71 [NSArray arrayWithObject:[NSColor clearColor]]]; 72 73 base::scoped_nsobject<AppsGridViewItem> itemPrototype( 74 [[AppsGridViewItem alloc] initWithSize:cellSize_]); 75 [[itemPrototype button] setTarget:gridController_]; 76 [[itemPrototype button] setAction:@selector(onItemClicked:)]; 77 78 [itemCollectionView setItemPrototype:itemPrototype]; 79 return itemCollectionView.autorelease(); 80} 81 82- (void)onMouseDownInPage:(NSCollectionView*)page 83 withEvent:(NSEvent*)theEvent { 84 size_t pageIndex = [gridController_ pageIndexForCollectionView:page]; 85 itemHitIndex_ = [self itemIndexForPage:pageIndex 86 hitWithEvent:theEvent]; 87 if (itemHitIndex_ == NSNotFound) 88 return; 89 90 mouseDownLocation_ = [theEvent locationInWindow]; 91 [[[gridController_ itemAtIndex:itemHitIndex_] view] mouseDown:theEvent]; 92} 93 94- (void)onMouseDragged:(NSEvent*)theEvent { 95 if (itemHitIndex_ == NSNotFound) 96 return; 97 98 if (dragging_) { 99 [self updateDrag:theEvent]; 100 return; 101 } 102 103 NSPoint mouseLocation = [theEvent locationInWindow]; 104 CGFloat deltaX = mouseLocation.x - mouseDownLocation_.x; 105 CGFloat deltaY = mouseLocation.y - mouseDownLocation_.y; 106 if (deltaX * deltaX + deltaY * deltaY > kDragThreshold * kDragThreshold) { 107 [self initiateDrag:theEvent]; 108 return; 109 } 110 111 [[[gridController_ itemAtIndex:itemHitIndex_] view] mouseDragged:theEvent]; 112} 113 114- (void)onMouseUp:(NSEvent*)theEvent { 115 if (itemHitIndex_ == NSNotFound) 116 return; 117 118 if (dragging_) { 119 [self completeDrag]; 120 return; 121 } 122 123 [[[gridController_ itemAtIndex:itemHitIndex_] view] mouseUp:theEvent]; 124} 125 126- (size_t)itemIndexForPage:(size_t)pageIndex 127 hitWithEvent:(NSEvent*)theEvent { 128 NSCollectionView* page = 129 [gridController_ collectionViewAtPageIndex:pageIndex]; 130 NSPoint pointInView = [page convertPoint:[theEvent locationInWindow] 131 fromView:nil]; 132 133 const size_t itemsInPage = [[page content] count]; 134 for (size_t indexInPage = 0; indexInPage < itemsInPage; ++indexInPage) { 135 if ([page mouse:pointInView 136 inRect:[page frameForItemAtIndex:indexInPage]]) { 137 return indexInPage + pageIndex * rows_ * columns_; 138 } 139 } 140 141 return NSNotFound; 142} 143 144- (void)initiateDrag:(NSEvent*)theEvent { 145 DCHECK_LT(itemHitIndex_, [gridController_ itemCount]); 146 dragging_ = YES; 147 148 if (!itemDragController_) { 149 itemDragController_.reset( 150 [[ItemDragController alloc] initWithGridCellSize:cellSize_]); 151 [[[gridController_ view] superview] addSubview:[itemDragController_ view]]; 152 } 153 154 [itemDragController_ initiate:[gridController_ itemAtIndex:itemHitIndex_] 155 mouseDownLocation:mouseDownLocation_ 156 currentLocation:[theEvent locationInWindow] 157 timestamp:[theEvent timestamp]]; 158 159 itemDragIndex_ = itemHitIndex_; 160 [self updateDrag:theEvent]; 161} 162 163- (void)updateDrag:(NSEvent*)theEvent { 164 [itemDragController_ update:[theEvent locationInWindow] 165 timestamp:[theEvent timestamp]]; 166 [gridController_ maybeChangePageForPoint:[theEvent locationInWindow]]; 167 168 size_t visiblePage = [gridController_ visiblePage]; 169 size_t itemIndexOver = [self itemIndexForPage:visiblePage 170 hitWithEvent:theEvent]; 171 DCHECK_NE(0u, [gridController_ itemCount]); 172 if (itemIndexOver == NSNotFound) { 173 if (visiblePage != [gridController_ pageCount] - 1) 174 return; 175 176 // If nothing was hit, but the last page is shown, move to the end. 177 itemIndexOver = [gridController_ itemCount] - 1; 178 } 179 180 if (itemDragIndex_ == itemIndexOver) 181 return; 182 183 [gridController_ moveItemInView:itemDragIndex_ 184 toItemIndex:itemIndexOver]; 185 // A new item may be created when moving between pages. Ensure it is hidden. 186 [[[gridController_ itemAtIndex:itemIndexOver] button] setHidden:YES]; 187 itemDragIndex_ = itemIndexOver; 188} 189 190- (void)cancelDrag { 191 if (!dragging_) 192 return; 193 194 [gridController_ moveItemInView:itemDragIndex_ 195 toItemIndex:itemHitIndex_]; 196 itemDragIndex_ = itemHitIndex_; 197 [self completeDrag]; 198 itemHitIndex_ = NSNotFound; // Ignore future mouse events for this drag. 199} 200 201- (void)completeDrag { 202 DCHECK_GE(itemDragIndex_, 0u); 203 [gridController_ cancelScrollTimer]; 204 AppsGridViewItem* item = [gridController_ itemAtIndex:itemDragIndex_]; 205 206 // The item could still be animating in the NSCollectionView, so ask it where 207 // it would place the item. 208 NSCollectionView* pageView = base::mac::ObjCCastStrict<NSCollectionView>( 209 [[item view] superview]); 210 size_t indexInPage = itemDragIndex_ % (rows_ * columns_); 211 NSPoint targetOrigin = [[[itemDragController_ view] superview] 212 convertPoint:[pageView frameForItemAtIndex:indexInPage].origin 213 fromView:pageView]; 214 215 [itemDragController_ complete:item 216 targetOrigin:targetOrigin]; 217 [gridController_ moveItemWithIndex:itemHitIndex_ 218 toModelIndex:itemDragIndex_]; 219 220 dragging_ = NO; 221} 222 223- (NSMenu*)menuForEvent:(NSEvent*)theEvent 224 inPage:(NSCollectionView*)page { 225 size_t pageIndex = [gridController_ pageIndexForCollectionView:page]; 226 size_t itemIndex = [self itemIndexForPage:pageIndex 227 hitWithEvent:theEvent]; 228 if (itemIndex == NSNotFound) 229 return nil; 230 231 return [[gridController_ itemAtIndex:itemIndex] contextMenu]; 232} 233 234@end 235 236@implementation GridCollectionView 237 238@synthesize factory = factory_; 239 240- (NSMenu*)menuForEvent:(NSEvent*)theEvent { 241 return [factory_ menuForEvent:theEvent 242 inPage:self]; 243} 244 245- (void)mouseDown:(NSEvent*)theEvent { 246 [factory_ onMouseDownInPage:self 247 withEvent:theEvent]; 248} 249 250- (void)mouseDragged:(NSEvent*)theEvent { 251 [factory_ onMouseDragged:theEvent]; 252} 253 254- (void)mouseUp:(NSEvent*)theEvent { 255 [factory_ onMouseUp:theEvent]; 256} 257 258- (BOOL)acceptsFirstResponder { 259 return NO; 260} 261 262@end 263