apps_grid_controller.mm revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#import "ui/app_list/cocoa/apps_grid_controller.h" 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/mac/foundation_util.h" 85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/app_list/app_list_model.h" 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/app_list/app_list_model_observer.h" 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/app_list/app_list_view_delegate.h" 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#import "ui/app_list/cocoa/apps_collection_view_drag_manager.h" 125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#import "ui/app_list/cocoa/apps_grid_view_item.h" 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#import "ui/app_list/cocoa/apps_pagination_model_observer.h" 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "ui/base/models/list_model_observer.h" 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace { 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// OSX app list has hardcoded rows and columns for now. 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const int kFixedRows = 4; 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const int kFixedColumns = 4; 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const int kItemsPerPage = kFixedRows * kFixedColumns; 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Padding space in pixels for fixed layout. 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const CGFloat kLeftRightPadding = 16; 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const CGFloat kTopPadding = 30; 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Preferred tile size when showing in fixed layout. These should be even 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// numbers to ensure that if they are grown 50% they remain integers. 295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const CGFloat kPreferredTileWidth = 88; 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const CGFloat kPreferredTileHeight = 98; 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const CGFloat kViewWidth = 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) kFixedColumns * kPreferredTileWidth + 2 * kLeftRightPadding; 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const CGFloat kViewHeight = kFixedRows * kPreferredTileHeight; 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)NSTimeInterval g_scroll_duration = 0.18; 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} // namespace 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@interface AppsGridController () 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Cancel a currently running scroll animation. 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (void)cancelScrollAnimation; 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Index of the page with the most content currently visible. 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (size_t)nearestPageIndex; 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Bootstrap the views this class controls. 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (void)loadAndSetView; 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (void)boundsDidChange:(NSNotification*)notification; 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Action for buttons in the grid. 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (void)onItemClicked:(id)sender; 555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (AppsGridViewItem*)itemAtPageIndex:(size_t)pageIndex 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) indexInPage:(size_t)indexInPage; 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Update the model in full, and rebuild subviews. 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (void)modelUpdated; 615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Return the button selected in first page with a selection. 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (NSButton*)selectedButton; 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// The scroll view holding the grid pages. 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (NSScrollView*)gridScrollView; 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (NSView*)pagesContainerView; 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Create any new pages after updating |items_|. 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (void)updatePages:(size_t)startItemIndex; 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (void)updatePageContent:(size_t)pageIndex 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) resetModel:(BOOL)resetModel; 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Bridged method for ui::ListModelObserver. 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (void)listItemsAdded:(size_t)start 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) count:(size_t)count; 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@end 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace app_list { 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class AppsGridDelegateBridge : public ui::ListModelObserver { 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public: 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) AppsGridDelegateBridge(AppsGridController* parent) : parent_(parent) {} 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private: 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Overridden from ui::ListModelObserver: 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) virtual void ListItemsAdded(size_t start, size_t count) OVERRIDE { 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [parent_ listItemsAdded:start count:count]; 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) virtual void ListItemsRemoved(size_t start, size_t count) OVERRIDE {} 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) virtual void ListItemMoved(size_t index, size_t target_index) OVERRIDE {} 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) virtual void ListItemsChanged(size_t start, size_t count) OVERRIDE { 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) NOTREACHED(); 975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) AppsGridController* parent_; // Weak, owns us. 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DISALLOW_COPY_AND_ASSIGN(AppsGridDelegateBridge); 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} // namespace app_list 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@implementation AppsGridController 1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)+ (void)setScrollAnimationDuration:(NSTimeInterval)duration { 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) g_scroll_duration = duration; 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)@synthesize paginationObserver = paginationObserver_; 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (id)init { 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if ((self = [super init])) { 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) bridge_.reset(new app_list::AppsGridDelegateBridge(self)); 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) NSSize cellSize = NSMakeSize(kPreferredTileWidth, kPreferredTileHeight); 1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) dragManager_.reset( 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [[AppsCollectionViewDragManager alloc] initWithCellSize:cellSize 1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) rows:kFixedRows 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) columns:kFixedColumns 1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) gridController:self]); 1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) pages_.reset([[NSMutableArray alloc] init]); 1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) items_.reset([[NSMutableArray alloc] init]); 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [self loadAndSetView]; 1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [self updatePages:0]; 1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return self; 1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (void)dealloc { 1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [[NSNotificationCenter defaultCenter] removeObserver:self]; 1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [self setModel:scoped_ptr<app_list::AppListModel>()]; 1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [super dealloc]; 1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (NSCollectionView*)collectionViewAtPageIndex:(size_t)pageIndex { 1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return [pages_ objectAtIndex:pageIndex]; 1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (size_t)pageIndexForCollectionView:(NSCollectionView*)page { 1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (size_t pageIndex = 0; pageIndex < [pages_ count]; ++pageIndex) { 1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (page == [self collectionViewAtPageIndex:pageIndex]) 1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return pageIndex; 1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return NSNotFound; 1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (app_list::AppListModel*)model { 1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return model_.get(); 1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (void)setModel:(scoped_ptr<app_list::AppListModel>)newModel { 1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (model_) { 1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) model_->apps()->RemoveObserver(bridge_.get()); 1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Since the model is about to be deleted, and the AppKit objects might be 1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // sitting in an NSAutoreleasePool, ensure there are no references to the 1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // model. 1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (size_t i = 0; i < [items_ count]; ++i) 1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [[self itemAtIndex:i] setModel:NULL]; 1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) model_.reset(newModel.release()); 1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (model_) 1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) model_->apps()->AddObserver(bridge_.get()); 1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [self modelUpdated]; 1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (void)setDelegate:(app_list::AppListViewDelegate*)newDelegate { 1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scoped_ptr<app_list::AppListModel> newModel(new app_list::AppListModel); 1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) delegate_ = newDelegate; 1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (delegate_) 1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) delegate_->SetModel(newModel.get()); // Populates items. 1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [self setModel:newModel.Pass()]; 1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (size_t)visiblePage { 1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return visiblePage_; 1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (void)activateSelection { 1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [[self selectedButton] performClick:self]; 1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (size_t)pageCount { 1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return [pages_ count]; 1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (size_t)itemCount { 1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return [items_ count]; 1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (void)scrollToPage:(size_t)pageIndex { 1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) NSClipView* clipView = [[self gridScrollView] contentView]; 1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) NSPoint newOrigin = [clipView bounds].origin; 1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Scrolling outside of this range is edge elasticity, which animates 2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // automatically. 2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if ((pageIndex == 0 && (newOrigin.x <= 0)) || 2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) (pageIndex + 1 == [self pageCount] && 2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) newOrigin.x >= pageIndex * kViewWidth)) { 2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) newOrigin.x = pageIndex * kViewWidth; 2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [NSAnimationContext beginGrouping]; 2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [[NSAnimationContext currentContext] setDuration:g_scroll_duration]; 2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [[clipView animator] setBoundsOrigin:newOrigin]; 2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [NSAnimationContext endGrouping]; 2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) animatingScroll_ = YES; 2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (void)cancelScrollAnimation { 2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) NSClipView* clipView = [[self gridScrollView] contentView]; 2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [NSAnimationContext beginGrouping]; 2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [[NSAnimationContext currentContext] setDuration:0]; 2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [[clipView animator] setBoundsOrigin:[clipView bounds].origin]; 2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) [NSAnimationContext endGrouping]; 2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) animatingScroll_ = NO; 2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)- (size_t)nearestPageIndex { 2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return lround( 2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) NSMinX([[[self gridScrollView] contentView] bounds]) / kViewWidth); 227} 228 229- (void)userScrolling:(BOOL)isScrolling { 230 if (isScrolling) { 231 if (animatingScroll_) 232 [self cancelScrollAnimation]; 233 } else { 234 [self scrollToPage:[self nearestPageIndex]]; 235 } 236} 237 238- (void)loadAndSetView { 239 scoped_nsobject<NSView> pagesContainer( 240 [[NSView alloc] initWithFrame:NSZeroRect]); 241 242 NSRect scrollFrame = NSMakeRect(0, 0, kViewWidth, kViewHeight + kTopPadding); 243 scoped_nsobject<ScrollViewWithNoScrollbars> scrollView( 244 [[ScrollViewWithNoScrollbars alloc] initWithFrame:scrollFrame]); 245 [scrollView setBorderType:NSNoBorder]; 246 [scrollView setLineScroll:kViewWidth]; 247 [scrollView setPageScroll:kViewWidth]; 248 [scrollView setDelegate:self]; 249 [scrollView setDocumentView:pagesContainer]; 250 [scrollView setDrawsBackground:NO]; 251 252 [[NSNotificationCenter defaultCenter] 253 addObserver:self 254 selector:@selector(boundsDidChange:) 255 name:NSViewBoundsDidChangeNotification 256 object:[scrollView contentView]]; 257 258 [self setView:scrollView]; 259} 260 261- (void)boundsDidChange:(NSNotification*)notification { 262 if ([self nearestPageIndex] == visiblePage_) 263 return; 264 265 // Clear any selection on the previous page (unless it has been removed). 266 if (visiblePage_ < [pages_ count]) { 267 [[self collectionViewAtPageIndex:visiblePage_] 268 setSelectionIndexes:[NSIndexSet indexSet]]; 269 } 270 visiblePage_ = [self nearestPageIndex]; 271 [paginationObserver_ selectedPageChanged:visiblePage_]; 272} 273 274- (void)onItemClicked:(id)sender { 275 for (size_t i = 0; i < [items_ count]; ++i) { 276 AppsGridViewItem* item = [self itemAtIndex:i]; 277 if ([[item button] isEqual:sender]) 278 delegate_->ActivateAppListItem([item model], 0); 279 } 280} 281 282- (AppsGridViewItem*)itemAtPageIndex:(size_t)pageIndex 283 indexInPage:(size_t)indexInPage { 284 return base::mac::ObjCCastStrict<AppsGridViewItem>( 285 [[self collectionViewAtPageIndex:pageIndex] itemAtIndex:indexInPage]); 286} 287 288- (AppsGridViewItem*)itemAtIndex:(size_t)itemIndex { 289 const size_t pageIndex = itemIndex / kItemsPerPage; 290 return [self itemAtPageIndex:pageIndex 291 indexInPage:itemIndex - pageIndex * kItemsPerPage]; 292} 293 294- (void)modelUpdated { 295 [items_ removeAllObjects]; 296 if (model_ && model_->apps()->item_count()) { 297 [self listItemsAdded:0 298 count:model_->apps()->item_count()]; 299 } else { 300 [self updatePages:0]; 301 } 302} 303 304- (NSButton*)selectedButton { 305 NSIndexSet* selection = nil; 306 size_t pageIndex = 0; 307 for (; pageIndex < [self pageCount]; ++pageIndex) { 308 selection = [[self collectionViewAtPageIndex:pageIndex] selectionIndexes]; 309 if ([selection count] > 0) 310 break; 311 } 312 313 if (pageIndex == [self pageCount]) 314 return nil; 315 316 return [[self itemAtPageIndex:pageIndex 317 indexInPage:[selection firstIndex]] button]; 318} 319 320- (NSScrollView*)gridScrollView { 321 return base::mac::ObjCCastStrict<NSScrollView>([self view]); 322} 323 324- (NSView*)pagesContainerView { 325 return [[self gridScrollView] documentView]; 326} 327 328- (void)updatePages:(size_t)startItemIndex { 329 // Note there is always at least one page. 330 size_t targetPages = 1; 331 if ([items_ count] != 0) 332 targetPages = ([items_ count] - 1) / kItemsPerPage + 1; 333 334 const size_t currentPages = [self pageCount]; 335 // First see if the number of pages have changed. 336 if (targetPages != currentPages) { 337 if (targetPages < currentPages) { 338 // Pages need to be removed. 339 [pages_ removeObjectsInRange:NSMakeRange(targetPages, 340 currentPages - targetPages)]; 341 } else { 342 // Pages need to be added. 343 for (size_t i = currentPages; i < targetPages; ++i) { 344 NSRect pageFrame = NSMakeRect( 345 kLeftRightPadding + kViewWidth * i, 0, 346 kViewWidth, kViewHeight); 347 [pages_ addObject:[dragManager_ makePageWithFrame:pageFrame]]; 348 } 349 } 350 351 [[self pagesContainerView] setSubviews:pages_]; 352 NSSize pagesSize = NSMakeSize(kViewWidth * targetPages, kViewHeight); 353 [[self pagesContainerView] setFrameSize:pagesSize]; 354 [paginationObserver_ totalPagesChanged]; 355 } 356 357 const size_t startPage = startItemIndex / kItemsPerPage; 358 // All pages on or after |startPage| may need items added or removed. 359 for (size_t pageIndex = startPage; pageIndex < targetPages; ++pageIndex) { 360 [self updatePageContent:pageIndex 361 resetModel:YES]; 362 } 363} 364 365- (void)updatePageContent:(size_t)pageIndex 366 resetModel:(BOOL)resetModel { 367 NSCollectionView* pageView = [self collectionViewAtPageIndex:pageIndex]; 368 if (resetModel) { 369 // Clear the models first, otherwise removed items could be autoreleased at 370 // an unknown point in the future, when the model owner may have gone away. 371 for (size_t i = 0; i < [[pageView content] count]; ++i) { 372 AppsGridViewItem* item = base::mac::ObjCCastStrict<AppsGridViewItem>( 373 [pageView itemAtIndex:i]); 374 [item setModel:NULL]; 375 } 376 } 377 378 NSRange inPageRange = NSIntersectionRange( 379 NSMakeRange(pageIndex * kItemsPerPage, kItemsPerPage), 380 NSMakeRange(0, [items_ count])); 381 NSArray* pageContent = [items_ subarrayWithRange:inPageRange]; 382 [pageView setContent:pageContent]; 383 if (!resetModel) 384 return; 385 386 for (size_t i = 0; i < [pageContent count]; ++i) { 387 AppsGridViewItem* item = base::mac::ObjCCastStrict<AppsGridViewItem>( 388 [pageView itemAtIndex:i]); 389 [item setModel:static_cast<app_list::AppListItemModel*>( 390 [[pageContent objectAtIndex:i] pointerValue])]; 391 } 392} 393 394- (void)moveItemForDrag:(size_t)fromIndex 395 toItemIndex:(size_t)toIndex { 396 scoped_nsobject<NSValue> item([[items_ objectAtIndex:fromIndex] retain]); 397 [items_ removeObjectAtIndex:fromIndex]; 398 [items_ insertObject:item 399 atIndex:toIndex]; 400 401 size_t fromPageIndex = fromIndex / kItemsPerPage; 402 size_t toPageIndex = toIndex / kItemsPerPage; 403 if (fromPageIndex == toPageIndex) { 404 [self updatePageContent:fromPageIndex 405 resetModel:NO]; // Just reorder items. 406 return; 407 } 408 409 if (fromPageIndex > toPageIndex) 410 std::swap(fromPageIndex, toPageIndex); 411 412 for (size_t i = fromPageIndex; i <= toPageIndex; ++i) { 413 [self updatePageContent:i 414 resetModel:YES]; 415 } 416 [[[self itemAtIndex:toIndex] button] setHidden:YES]; 417} 418 419// Compare with views implementation in AppsGridView::MoveItemInModel(). 420- (void)moveItemWithIndex:(size_t)itemIndex 421 toModelIndex:(size_t)modelIndex { 422 // Ingore no-op moves. Note that this is always the case when canceled. 423 if (itemIndex == modelIndex) 424 return; 425 426 model_->apps()->RemoveObserver(bridge_.get()); 427 model_->apps()->Move(itemIndex, modelIndex); 428 model_->apps()->AddObserver(bridge_.get()); 429} 430 431- (AppsCollectionViewDragManager*)dragManager { 432 return dragManager_; 433} 434 435- (void)listItemsAdded:(size_t)start 436 count:(size_t)count { 437 // Cancel any drag, to ensure the model stays consistent. 438 [dragManager_ cancelDrag]; 439 440 for (size_t i = start; i < start + count; ++i) { 441 app_list::AppListItemModel* itemModel = model_->apps()->GetItemAt(i); 442 [items_ insertObject:[NSValue valueWithPointer:itemModel] 443 atIndex:i]; 444 } 445 446 [self updatePages:start]; 447} 448 449@end 450