Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / ui / app_list / cocoa / app_list_view_controller.mm
blob0cc2f2d71199e8a0da0102ae31e93cc7d2236549
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.
5 #import "ui/app_list/cocoa/app_list_view_controller.h"
7 #include "base/mac/foundation_util.h"
8 #include "base/mac/mac_util.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/sys_string_conversions.h"
11 #include "skia/ext/skia_utils_mac.h"
12 #include "ui/app_list/app_list_constants.h"
13 #include "ui/app_list/app_list_model.h"
14 #include "ui/app_list/app_list_view_delegate.h"
15 #include "ui/app_list/app_list_view_delegate_observer.h"
16 #import "ui/app_list/cocoa/app_list_pager_view.h"
17 #import "ui/app_list/cocoa/apps_grid_controller.h"
18 #import "ui/base/cocoa/flipped_view.h"
19 #include "ui/app_list/search_box_model.h"
20 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
22 namespace {
24 // The roundedness of the corners of the bubble.
25 const CGFloat kBubbleCornerRadius = 3;
27 // Height of the pager.
28 const CGFloat kPagerPreferredHeight = 57;
30 // Height of separator line drawn between the searchbox and grid view.
31 const CGFloat kTopSeparatorSize = 1;
33 // Height of the search input.
34 const CGFloat kSearchInputHeight = 48;
36 // Minimum margin on either side of the pager. If the pager grows beyond this,
37 // the segment size is reduced.
38 const CGFloat kMinPagerMargin = 40;
39 // Maximum width of a single segment.
40 const CGFloat kMaxSegmentWidth = 80;
42 // Duration of the animation for sliding in and out search results.
43 const NSTimeInterval kResultsAnimationDuration = 0.2;
45 }  // namespace
47 @interface BackgroundView : FlippedView;
48 @end
50 @implementation BackgroundView
52 - (void)drawRect:(NSRect)dirtyRect {
53   gfx::ScopedNSGraphicsContextSaveGState context;
54   NSRect boundsRect = [self bounds];
55   NSRect searchAreaRect = NSMakeRect(0, 0,
56                                      NSWidth(boundsRect), kSearchInputHeight);
57   NSRect separatorRect = NSMakeRect(0, NSMaxY(searchAreaRect),
58                                     NSWidth(boundsRect), kTopSeparatorSize);
60   [[NSBezierPath bezierPathWithRoundedRect:boundsRect
61                                    xRadius:kBubbleCornerRadius
62                                    yRadius:kBubbleCornerRadius] addClip];
64   [gfx::SkColorToSRGBNSColor(app_list::kContentsBackgroundColor) set];
65   NSRectFill(boundsRect);
66   [gfx::SkColorToSRGBNSColor(app_list::kSearchBoxBackground) set];
67   NSRectFill(searchAreaRect);
68   [gfx::SkColorToSRGBNSColor(app_list::kTopSeparatorColor) set];
69   NSRectFill(separatorRect);
72 @end
74 @interface AppListViewController ()
76 - (void)loadAndSetView;
77 - (void)revealSearchResults:(BOOL)show;
79 @end
81 namespace app_list {
83 class AppListModelObserverBridge : public AppListViewDelegateObserver {
84  public:
85   AppListModelObserverBridge(AppListViewController* parent);
86   virtual ~AppListModelObserverBridge();
88  private:
89   // Overridden from app_list::AppListViewDelegateObserver:
90   virtual void OnProfilesChanged() OVERRIDE;
92   AppListViewController* parent_;  // Weak. Owns us.
94   DISALLOW_COPY_AND_ASSIGN(AppListModelObserverBridge);
97 AppListModelObserverBridge::AppListModelObserverBridge(
98     AppListViewController* parent)
99     : parent_(parent) {
100   [parent_ delegate]->AddObserver(this);
103 AppListModelObserverBridge::~AppListModelObserverBridge() {
104   [parent_ delegate]->RemoveObserver(this);
107 void AppListModelObserverBridge::OnProfilesChanged() {
108   [parent_ onProfilesChanged];
111 }  // namespace app_list
113 @implementation AppListViewController
115 - (id)init {
116   if ((self = [super init])) {
117     appsGridController_.reset([[AppsGridController alloc] init]);
118     [self loadAndSetView];
120     [self totalPagesChanged];
121     [self selectedPageChanged:0];
122     [appsGridController_ setPaginationObserver:self];
123   }
124   return self;
127 - (void)dealloc {
128   // Ensure that setDelegate(NULL) has been called before destruction, because
129   // dealloc can be called at odd times, and Objective C destruction order does
130   // not properly tear down these dependencies.
131   DCHECK(delegate_ == NULL);
132   [appsGridController_ setPaginationObserver:nil];
133   [super dealloc];
136 - (AppsSearchBoxController*)searchBoxController {
137   return appsSearchBoxController_;
140 - (BOOL)showingSearchResults {
141   return showingSearchResults_;
144 - (AppsGridController*)appsGridController {
145   return appsGridController_;
148 - (NSSegmentedControl*)pagerControl {
149   return pagerControl_;
152 - (NSView*)backgroundView {
153   return backgroundView_;
156 - (app_list::AppListViewDelegate*)delegate {
157   return delegate_.get();
160 - (void)setDelegate:(scoped_ptr<app_list::AppListViewDelegate>)newDelegate {
161   if (delegate_) {
162     // Ensure the search box is cleared when switching profiles.
163     if ([self searchBoxModel])
164       [self searchBoxModel]->SetText(base::string16());
166     // First clean up, in reverse order.
167     app_list_model_observer_bridge_.reset();
168     [appsSearchResultsController_ setDelegate:nil];
169     [appsSearchBoxController_ setDelegate:nil];
170     [appsGridController_ setDelegate:nil];
171   }
172   delegate_.reset(newDelegate.release());
173   if (delegate_) {
174     [loadingIndicator_ stopAnimation:self];
175   } else {
176     [loadingIndicator_ startAnimation:self];
177     return;
178   }
180   [appsGridController_ setDelegate:delegate_.get()];
181   [appsSearchBoxController_ setDelegate:self];
182   [appsSearchResultsController_ setDelegate:self];
183   app_list_model_observer_bridge_.reset(
184       new app_list::AppListModelObserverBridge(self));
185   [self onProfilesChanged];
188 -(void)loadAndSetView {
189   pagerControl_.reset([[AppListPagerView alloc] init]);
190   [pagerControl_ setTarget:appsGridController_];
191   [pagerControl_ setAction:@selector(onPagerClicked:)];
193   NSRect gridFrame = [[appsGridController_ view] frame];
194   NSRect contentsRect = NSMakeRect(0, kSearchInputHeight + kTopSeparatorSize,
195       NSWidth(gridFrame), NSHeight(gridFrame) + kPagerPreferredHeight -
196           [AppsGridController scrollerPadding]);
198   contentsView_.reset([[FlippedView alloc] initWithFrame:contentsRect]);
200   // The contents view contains animations both from an NSCollectionView and the
201   // app list's own transitive drag layers. On Mavericks, the subviews need to
202   // have access to a compositing layer they can share. Otherwise the compositor
203   // makes tearing artifacts. However, doing this on Mountain Lion or earler
204   // results in flickering whilst an item is installing.
205   if (base::mac::IsOSMavericksOrLater())
206     [contentsView_ setWantsLayer:YES];
208   backgroundView_.reset(
209       [[BackgroundView alloc] initWithFrame:
210               NSMakeRect(0, 0, NSMaxX(contentsRect), NSMaxY(contentsRect))]);
211   appsSearchBoxController_.reset(
212       [[AppsSearchBoxController alloc] initWithFrame:
213           NSMakeRect(0, 0, NSWidth(contentsRect), kSearchInputHeight)]);
214   appsSearchResultsController_.reset(
215       [[AppsSearchResultsController alloc] initWithAppsSearchResultsFrameSize:
216           [contentsView_ bounds].size]);
217   base::scoped_nsobject<NSView> containerView(
218       [[NSView alloc] initWithFrame:[backgroundView_ frame]]);
220   loadingIndicator_.reset(
221       [[NSProgressIndicator alloc] initWithFrame:NSZeroRect]);
222   [loadingIndicator_ setStyle:NSProgressIndicatorSpinningStyle];
223   [loadingIndicator_ sizeToFit];
224   NSRect indicatorRect = [loadingIndicator_ frame];
225   indicatorRect.origin.x = NSWidth(contentsRect) / 2 - NSMidX(indicatorRect);
226   indicatorRect.origin.y = NSHeight(contentsRect) / 2 - NSMidY(indicatorRect);
227   [loadingIndicator_ setFrame:indicatorRect];
228   [loadingIndicator_ setDisplayedWhenStopped:NO];
229   [loadingIndicator_ startAnimation:self];
231   [contentsView_ addSubview:[appsGridController_ view]];
232   [contentsView_ addSubview:pagerControl_];
233   [contentsView_ addSubview:loadingIndicator_];
234   [backgroundView_ addSubview:contentsView_];
235   [backgroundView_ addSubview:[appsSearchResultsController_ view]];
236   [backgroundView_ addSubview:[appsSearchBoxController_ view]];
237   [containerView addSubview:backgroundView_];
238   [self setView:containerView];
241 - (void)revealSearchResults:(BOOL)show {
242   if (show == showingSearchResults_)
243     return;
245   showingSearchResults_ = show;
246   NSSize contentsSize = [contentsView_ frame].size;
247   NSRect resultsTargetRect = NSMakeRect(
248       0, kSearchInputHeight + kTopSeparatorSize,
249       contentsSize.width, contentsSize.height);
250   NSRect contentsTargetRect = resultsTargetRect;
252   // Shows results by sliding the grid and pager down to the bottom of the view.
253   // Hides results by collapsing the search results container to a height of 0.
254   if (show)
255     contentsTargetRect.origin.y += NSHeight(contentsTargetRect);
256   else
257     resultsTargetRect.size.height = 0;
259   [[NSAnimationContext currentContext] setDuration:kResultsAnimationDuration];
260   [[contentsView_ animator] setFrame:contentsTargetRect];
261   [[[appsSearchResultsController_ view] animator] setFrame:resultsTargetRect];
264 - (void)totalPagesChanged {
265   size_t pageCount = [appsGridController_ pageCount];
266   [pagerControl_ setSegmentCount:pageCount];
268   NSRect viewFrame = [[pagerControl_ superview] bounds];
269   CGFloat segmentWidth = std::min(
270       kMaxSegmentWidth,
271       (viewFrame.size.width - 2 * kMinPagerMargin) / pageCount);
273   for (size_t i = 0; i < pageCount; ++i) {
274     [pagerControl_ setWidth:segmentWidth
275                  forSegment:i];
276     [[pagerControl_ cell] setTag:i
277                       forSegment:i];
278   }
280   // Center in view.
281   [pagerControl_ sizeToFit];
282   [pagerControl_ setFrame:
283       NSMakeRect(NSMidX(viewFrame) - NSMidX([pagerControl_ bounds]),
284                  viewFrame.size.height - kPagerPreferredHeight,
285                  [pagerControl_ bounds].size.width,
286                  kPagerPreferredHeight)];
289 - (void)selectedPageChanged:(int)newSelected {
290   [pagerControl_ selectSegmentWithTag:newSelected];
293 - (void)pageVisibilityChanged {
294   [pagerControl_ setNeedsDisplay:YES];
297 - (NSInteger)pagerSegmentAtLocation:(NSPoint)locationInWindow {
298   return [pagerControl_ findAndHighlightSegmentAtLocation:locationInWindow];
301 - (app_list::SearchBoxModel*)searchBoxModel {
302   app_list::AppListModel* appListModel = [appsGridController_ model];
303   return appListModel ? appListModel->search_box() : NULL;
306 - (app_list::AppListViewDelegate*)appListDelegate {
307   return [self delegate];
310 - (BOOL)control:(NSControl*)control
311                textView:(NSTextView*)textView
312     doCommandBySelector:(SEL)command {
313   if (showingSearchResults_)
314     return [appsSearchResultsController_ handleCommandBySelector:command];
316   // If anything has been written, let the search view handle it.
317   if ([[control stringValue] length] > 0)
318     return NO;
320   // Handle escape.
321   if (command == @selector(complete:) ||
322       command == @selector(cancel:) ||
323       command == @selector(cancelOperation:)) {
324     if (delegate_)
325       delegate_->Dismiss();
326     return YES;
327   }
329   // Possibly handle grid navigation.
330   return [appsGridController_ handleCommandBySelector:command];
333 - (void)modelTextDidChange {
334   app_list::SearchBoxModel* searchBoxModel = [self searchBoxModel];
335   if (!searchBoxModel || !delegate_)
336     return;
338   base::string16 query;
339   base::TrimWhitespace(searchBoxModel->text(), base::TRIM_ALL, &query);
340   BOOL shouldShowSearch = !query.empty();
341   [self revealSearchResults:shouldShowSearch];
342   if (shouldShowSearch)
343     delegate_->StartSearch();
344   else
345     delegate_->StopSearch();
348 - (app_list::AppListModel*)appListModel {
349   return [appsGridController_ model];
352 - (void)openResult:(app_list::SearchResult*)result {
353   if (delegate_) {
354     delegate_->OpenSearchResult(
355         result, false /* auto_launch */, 0 /* event flags */);
356   }
359 - (void)redoSearch {
360   [self modelTextDidChange];
363 - (void)onProfilesChanged {
364   [appsSearchBoxController_ rebuildMenu];
367 @end