Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / app_list / cocoa / app_list_view_controller.mm
blob8f337ce987dd0c801c28cf0bdc15a05bcd4a5186
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 #include "ui/app_list/search_box_model.h"
19 #import "ui/base/cocoa/flipped_view.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   ~AppListModelObserverBridge() override;
88  private:
89   // Overridden from app_list::AppListViewDelegateObserver:
90   void OnProfilesChanged() override;
91   void OnShutdown() override;
93   AppListViewController* parent_;  // Weak. Owns us.
95   DISALLOW_COPY_AND_ASSIGN(AppListModelObserverBridge);
98 AppListModelObserverBridge::AppListModelObserverBridge(
99     AppListViewController* parent)
100     : parent_(parent) {
101   [parent_ delegate]->AddObserver(this);
104 AppListModelObserverBridge::~AppListModelObserverBridge() {
105   [parent_ delegate]->RemoveObserver(this);
108 void AppListModelObserverBridge::OnProfilesChanged() {
109   [parent_ onProfilesChanged];
112 void AppListModelObserverBridge::OnShutdown() {
113   [parent_ setDelegate:nil];
116 }  // namespace app_list
118 @implementation AppListViewController
120 - (id)init {
121   if ((self = [super init])) {
122     appsGridController_.reset([[AppsGridController alloc] init]);
123     [self loadAndSetView];
125     [self totalPagesChanged];
126     [self selectedPageChanged:0];
127     [appsGridController_ setPaginationObserver:self];
128   }
129   return self;
132 - (void)dealloc {
133   // Ensure that setDelegate(NULL) has been called before destruction, because
134   // dealloc can be called at odd times, and Objective C destruction order does
135   // not properly tear down these dependencies.
136   DCHECK(delegate_ == NULL);
137   [appsGridController_ setPaginationObserver:nil];
138   [super dealloc];
141 - (AppsSearchBoxController*)searchBoxController {
142   return appsSearchBoxController_;
145 - (BOOL)showingSearchResults {
146   return showingSearchResults_;
149 - (AppsGridController*)appsGridController {
150   return appsGridController_;
153 - (NSSegmentedControl*)pagerControl {
154   return pagerControl_;
157 - (NSView*)backgroundView {
158   return backgroundView_;
161 - (app_list::AppListViewDelegate*)delegate {
162   return delegate_;
165 - (void)setDelegate:(app_list::AppListViewDelegate*)newDelegate {
166   if (delegate_) {
167     // Ensure the search box is cleared when switching profiles.
168     if ([self searchBoxModel])
169       [self searchBoxModel]->SetText(base::string16());
171     // First clean up, in reverse order.
172     app_list_model_observer_bridge_.reset();
173     [appsSearchResultsController_ setDelegate:nil];
174     [appsSearchBoxController_ setDelegate:nil];
175     [appsGridController_ setDelegate:nil];
176   }
177   delegate_ = newDelegate;
178   if (delegate_) {
179     [loadingIndicator_ stopAnimation:self];
180   } else {
181     [loadingIndicator_ startAnimation:self];
182     return;
183   }
185   [appsGridController_ setDelegate:delegate_];
186   [appsSearchBoxController_ setDelegate:self];
187   [appsSearchResultsController_ setDelegate:self];
188   app_list_model_observer_bridge_.reset(
189       new app_list::AppListModelObserverBridge(self));
190   [self onProfilesChanged];
193 -(void)loadAndSetView {
194   pagerControl_.reset([[AppListPagerView alloc] init]);
195   [pagerControl_ setTarget:appsGridController_];
196   [pagerControl_ setAction:@selector(onPagerClicked:)];
198   NSRect gridFrame = [[appsGridController_ view] frame];
199   NSRect contentsRect = NSMakeRect(0, kSearchInputHeight + kTopSeparatorSize,
200       NSWidth(gridFrame), NSHeight(gridFrame) + kPagerPreferredHeight -
201           [AppsGridController scrollerPadding]);
203   contentsView_.reset([[FlippedView alloc] initWithFrame:contentsRect]);
205   // The contents view contains animations both from an NSCollectionView and the
206   // app list's own transitive drag layers. On Mavericks, the subviews need to
207   // have access to a compositing layer they can share. Otherwise the compositor
208   // makes tearing artifacts. However, doing this on Mountain Lion or earler
209   // results in flickering whilst an item is installing.
210   if (base::mac::IsOSMavericksOrLater())
211     [contentsView_ setWantsLayer:YES];
213   backgroundView_.reset(
214       [[BackgroundView alloc] initWithFrame:
215               NSMakeRect(0, 0, NSMaxX(contentsRect), NSMaxY(contentsRect))]);
216   appsSearchBoxController_.reset(
217       [[AppsSearchBoxController alloc] initWithFrame:
218           NSMakeRect(0, 0, NSWidth(contentsRect), kSearchInputHeight)]);
219   appsSearchResultsController_.reset(
220       [[AppsSearchResultsController alloc] initWithAppsSearchResultsFrameSize:
221           [contentsView_ bounds].size]);
222   base::scoped_nsobject<NSView> containerView(
223       [[NSView alloc] initWithFrame:[backgroundView_ frame]]);
225   loadingIndicator_.reset(
226       [[NSProgressIndicator alloc] initWithFrame:NSZeroRect]);
227   [loadingIndicator_ setStyle:NSProgressIndicatorSpinningStyle];
228   [loadingIndicator_ sizeToFit];
229   NSRect indicatorRect = [loadingIndicator_ frame];
230   indicatorRect.origin.x = NSWidth(contentsRect) / 2 - NSMidX(indicatorRect);
231   indicatorRect.origin.y = NSHeight(contentsRect) / 2 - NSMidY(indicatorRect);
232   [loadingIndicator_ setFrame:indicatorRect];
233   [loadingIndicator_ setDisplayedWhenStopped:NO];
234   [loadingIndicator_ startAnimation:self];
236   [contentsView_ addSubview:[appsGridController_ view]];
237   [contentsView_ addSubview:pagerControl_];
238   [contentsView_ addSubview:loadingIndicator_];
239   [backgroundView_ addSubview:contentsView_];
240   [backgroundView_ addSubview:[appsSearchResultsController_ view]];
241   [backgroundView_ addSubview:[appsSearchBoxController_ view]];
242   [containerView addSubview:backgroundView_];
243   [self setView:containerView];
246 - (void)revealSearchResults:(BOOL)show {
247   if (show == showingSearchResults_)
248     return;
250   showingSearchResults_ = show;
251   NSSize contentsSize = [contentsView_ frame].size;
252   NSRect resultsTargetRect = NSMakeRect(
253       0, kSearchInputHeight + kTopSeparatorSize,
254       contentsSize.width, contentsSize.height);
255   NSRect contentsTargetRect = resultsTargetRect;
257   // Shows results by sliding the grid and pager down to the bottom of the view.
258   // Hides results by collapsing the search results container to a height of 0.
259   if (show)
260     contentsTargetRect.origin.y += NSHeight(contentsTargetRect);
261   else
262     resultsTargetRect.size.height = 0;
264   [[NSAnimationContext currentContext] setDuration:kResultsAnimationDuration];
265   [[contentsView_ animator] setFrame:contentsTargetRect];
266   [[[appsSearchResultsController_ view] animator] setFrame:resultsTargetRect];
269 - (void)totalPagesChanged {
270   size_t pageCount = [appsGridController_ pageCount];
271   [pagerControl_ setSegmentCount:pageCount];
273   NSRect viewFrame = [[pagerControl_ superview] bounds];
274   CGFloat segmentWidth = std::min(
275       kMaxSegmentWidth,
276       (viewFrame.size.width - 2 * kMinPagerMargin) / pageCount);
278   for (size_t i = 0; i < pageCount; ++i) {
279     [pagerControl_ setWidth:segmentWidth
280                  forSegment:i];
281     [[pagerControl_ cell] setTag:i
282                       forSegment:i];
283   }
285   // Center in view.
286   [pagerControl_ sizeToFit];
287   [pagerControl_ setFrame:
288       NSMakeRect(NSMidX(viewFrame) - NSMidX([pagerControl_ bounds]),
289                  viewFrame.size.height - kPagerPreferredHeight,
290                  [pagerControl_ bounds].size.width,
291                  kPagerPreferredHeight)];
294 - (void)selectedPageChanged:(int)newSelected {
295   [pagerControl_ selectSegmentWithTag:newSelected];
298 - (void)pageVisibilityChanged {
299   [pagerControl_ setNeedsDisplay:YES];
302 - (NSInteger)pagerSegmentAtLocation:(NSPoint)locationInWindow {
303   return [pagerControl_ findAndHighlightSegmentAtLocation:locationInWindow];
306 - (app_list::SearchBoxModel*)searchBoxModel {
307   app_list::AppListModel* appListModel = [appsGridController_ model];
308   return appListModel ? appListModel->search_box() : NULL;
311 - (app_list::AppListViewDelegate*)appListDelegate {
312   return [self delegate];
315 - (BOOL)control:(NSControl*)control
316                textView:(NSTextView*)textView
317     doCommandBySelector:(SEL)command {
318   if (showingSearchResults_)
319     return [appsSearchResultsController_ handleCommandBySelector:command];
321   // If anything has been written, let the search view handle it.
322   if ([[control stringValue] length] > 0)
323     return NO;
325   // Handle escape.
326   if (command == @selector(complete:) ||
327       command == @selector(cancel:) ||
328       command == @selector(cancelOperation:)) {
329     if (delegate_)
330       delegate_->Dismiss();
331     return YES;
332   }
334   // Possibly handle grid navigation.
335   return [appsGridController_ handleCommandBySelector:command];
338 - (void)modelTextDidChange {
339   app_list::SearchBoxModel* searchBoxModel = [self searchBoxModel];
340   if (!searchBoxModel || !delegate_)
341     return;
343   base::string16 query;
344   base::TrimWhitespace(searchBoxModel->text(), base::TRIM_ALL, &query);
345   BOOL shouldShowSearch = !query.empty();
346   [self revealSearchResults:shouldShowSearch];
347   if (shouldShowSearch)
348     delegate_->StartSearch();
349   else
350     delegate_->StopSearch();
353 - (app_list::AppListModel*)appListModel {
354   return [appsGridController_ model];
357 - (void)openResult:(app_list::SearchResult*)result {
358   if (delegate_) {
359     delegate_->OpenSearchResult(
360         result, false /* auto_launch */, 0 /* event flags */);
361   }
364 - (void)onProfilesChanged {
365   [appsSearchBoxController_ rebuildMenu];
368 @end