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