app_list_view_controller.mm revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
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/app_list_view_controller.h"
6
7#include "base/mac/foundation_util.h"
8#include "base/strings/string_util.h"
9#include "base/strings/sys_string_conversions.h"
10#include "skia/ext/skia_utils_mac.h"
11#include "ui/app_list/app_list_constants.h"
12#include "ui/app_list/app_list_model.h"
13#include "ui/app_list/app_list_view_delegate.h"
14#include "ui/app_list/app_list_view_delegate_observer.h"
15#include "ui/app_list/signin_delegate.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/app_list/cocoa/signin_view_controller.h"
19#import "ui/base/cocoa/flipped_view.h"
20#include "ui/app_list/search_box_model.h"
21#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
22
23namespace {
24
25// The roundedness of the corners of the bubble.
26const CGFloat kBubbleCornerRadius = 3;
27
28// Height of the pager.
29const CGFloat kPagerPreferredHeight = 57;
30
31// Height of separator line drawn between the searchbox and grid view.
32const CGFloat kTopSeparatorSize = 1;
33
34// Height of the search input.
35const CGFloat kSearchInputHeight = 48;
36
37// Minimum margin on either side of the pager. If the pager grows beyond this,
38// the segment size is reduced.
39const CGFloat kMinPagerMargin = 40;
40// Maximum width of a single segment.
41const CGFloat kMaxSegmentWidth = 80;
42
43// Duration of the animation for sliding in and out search results.
44const NSTimeInterval kResultsAnimationDuration = 0.2;
45
46}  // namespace
47
48@interface BackgroundView : FlippedView;
49@end
50
51@implementation BackgroundView
52
53- (void)drawRect:(NSRect)dirtyRect {
54  gfx::ScopedNSGraphicsContextSaveGState context;
55  NSRect boundsRect = [self bounds];
56  NSRect searchAreaRect = NSMakeRect(0, 0,
57                                     NSWidth(boundsRect), kSearchInputHeight);
58  NSRect separatorRect = NSMakeRect(0, NSMaxY(searchAreaRect),
59                                    NSWidth(boundsRect), kTopSeparatorSize);
60
61  [[NSBezierPath bezierPathWithRoundedRect:boundsRect
62                                   xRadius:kBubbleCornerRadius
63                                   yRadius:kBubbleCornerRadius] addClip];
64
65  [gfx::SkColorToSRGBNSColor(app_list::kContentsBackgroundColor) set];
66  NSRectFill(boundsRect);
67  [gfx::SkColorToSRGBNSColor(app_list::kSearchBoxBackground) set];
68  NSRectFill(searchAreaRect);
69  [gfx::SkColorToSRGBNSColor(app_list::kTopSeparatorColor) set];
70  NSRectFill(separatorRect);
71}
72
73@end
74
75@interface AppListViewController ()
76
77- (void)loadAndSetView;
78- (void)revealSearchResults:(BOOL)show;
79
80@end
81
82namespace app_list {
83
84class AppListModelObserverBridge : public AppListViewDelegateObserver {
85 public:
86  AppListModelObserverBridge(AppListViewController* parent);
87  virtual ~AppListModelObserverBridge();
88
89 private:
90  // Overridden from app_list::AppListViewDelegateObserver:
91  virtual void OnProfilesChanged() OVERRIDE;
92
93  AppListViewController* parent_;  // Weak. Owns us.
94
95  DISALLOW_COPY_AND_ASSIGN(AppListModelObserverBridge);
96};
97
98AppListModelObserverBridge::AppListModelObserverBridge(
99    AppListViewController* parent)
100    : parent_(parent) {
101  [parent_ delegate]->AddObserver(this);
102}
103
104AppListModelObserverBridge::~AppListModelObserverBridge() {
105  [parent_ delegate]->RemoveObserver(this);
106}
107
108void AppListModelObserverBridge::OnProfilesChanged() {
109  [parent_ onProfilesChanged];
110}
111
112}  // namespace app_list
113
114@implementation AppListViewController
115
116- (id)init {
117  if ((self = [super init])) {
118    appsGridController_.reset([[AppsGridController alloc] init]);
119    [self loadAndSetView];
120
121    [self totalPagesChanged];
122    [self selectedPageChanged:0];
123    [appsGridController_ setPaginationObserver:self];
124  }
125  return self;
126}
127
128- (void)dealloc {
129  // Ensure that setDelegate(NULL) has been called before destruction, because
130  // dealloc can be called at odd times, and Objective C destruction order does
131  // not properly tear down these dependencies.
132  DCHECK(delegate_ == NULL);
133  [appsGridController_ setPaginationObserver:nil];
134  [super dealloc];
135}
136
137- (AppsSearchBoxController*)searchBoxController {
138  return appsSearchBoxController_;
139}
140
141- (BOOL)showingSearchResults {
142  return showingSearchResults_;
143}
144
145- (AppsGridController*)appsGridController {
146  return appsGridController_;
147}
148
149- (NSSegmentedControl*)pagerControl {
150  return pagerControl_;
151}
152
153- (NSView*)backgroundView {
154  return backgroundView_;
155}
156
157- (app_list::AppListViewDelegate*)delegate {
158  return delegate_.get();
159}
160
161- (void)setDelegate:(scoped_ptr<app_list::AppListViewDelegate>)newDelegate {
162  if (delegate_) {
163    // First clean up, in reverse order.
164    app_list_model_observer_bridge_.reset();
165    [appsSearchResultsController_ setDelegate:nil];
166    [appsSearchBoxController_ setDelegate:nil];
167    [appsGridController_ setDelegate:nil];
168  }
169  delegate_.reset(newDelegate.release());
170  if (!delegate_)
171    return;
172  [appsGridController_ setDelegate:delegate_.get()];
173  [appsSearchBoxController_ setDelegate:self];
174  [appsSearchResultsController_ setDelegate:self];
175  app_list_model_observer_bridge_.reset(
176      new app_list::AppListModelObserverBridge(self));
177  [self onProfilesChanged];
178}
179
180-(void)loadAndSetView {
181  pagerControl_.reset([[AppListPagerView alloc] init]);
182  [pagerControl_ setTarget:appsGridController_];
183  [pagerControl_ setAction:@selector(onPagerClicked:)];
184
185  NSRect gridFrame = [[appsGridController_ view] frame];
186  NSRect contentsRect = NSMakeRect(0, kSearchInputHeight + kTopSeparatorSize,
187      NSWidth(gridFrame), NSHeight(gridFrame) + kPagerPreferredHeight -
188          [AppsGridController scrollerPadding]);
189
190  contentsView_.reset([[FlippedView alloc] initWithFrame:contentsRect]);
191  backgroundView_.reset(
192      [[BackgroundView alloc] initWithFrame:
193              NSMakeRect(0, 0, NSMaxX(contentsRect), NSMaxY(contentsRect))]);
194  appsSearchBoxController_.reset(
195      [[AppsSearchBoxController alloc] initWithFrame:
196          NSMakeRect(0, 0, NSWidth(contentsRect), kSearchInputHeight)]);
197  appsSearchResultsController_.reset(
198      [[AppsSearchResultsController alloc] initWithAppsSearchResultsFrameSize:
199          [contentsView_ bounds].size]);
200  base::scoped_nsobject<NSView> containerView(
201      [[NSView alloc] initWithFrame:[backgroundView_ frame]]);
202
203  [contentsView_ addSubview:[appsGridController_ view]];
204  [contentsView_ addSubview:pagerControl_];
205  [backgroundView_ addSubview:contentsView_];
206  [backgroundView_ addSubview:[appsSearchResultsController_ view]];
207  [backgroundView_ addSubview:[appsSearchBoxController_ view]];
208  [containerView addSubview:backgroundView_];
209  [self setView:containerView];
210}
211
212- (void)revealSearchResults:(BOOL)show {
213  if (show == showingSearchResults_)
214    return;
215
216  showingSearchResults_ = show;
217  NSSize contentsSize = [contentsView_ frame].size;
218  NSRect resultsTargetRect = NSMakeRect(
219      0, kSearchInputHeight + kTopSeparatorSize,
220      contentsSize.width, contentsSize.height);
221  NSRect contentsTargetRect = resultsTargetRect;
222
223  // Shows results by sliding the grid and pager down to the bottom of the view.
224  // Hides results by collapsing the search results container to a height of 0.
225  if (show)
226    contentsTargetRect.origin.y += NSHeight(contentsTargetRect);
227  else
228    resultsTargetRect.size.height = 0;
229
230  [[NSAnimationContext currentContext] setDuration:kResultsAnimationDuration];
231  [[contentsView_ animator] setFrame:contentsTargetRect];
232  [[[appsSearchResultsController_ view] animator] setFrame:resultsTargetRect];
233}
234
235- (void)totalPagesChanged {
236  size_t pageCount = [appsGridController_ pageCount];
237  [pagerControl_ setSegmentCount:pageCount];
238
239  NSRect viewFrame = [[pagerControl_ superview] bounds];
240  CGFloat segmentWidth = std::min(
241      kMaxSegmentWidth,
242      (viewFrame.size.width - 2 * kMinPagerMargin) / pageCount);
243
244  for (size_t i = 0; i < pageCount; ++i) {
245    [pagerControl_ setWidth:segmentWidth
246                 forSegment:i];
247    [[pagerControl_ cell] setTag:i
248                      forSegment:i];
249  }
250
251  // Center in view.
252  [pagerControl_ sizeToFit];
253  [pagerControl_ setFrame:
254      NSMakeRect(NSMidX(viewFrame) - NSMidX([pagerControl_ bounds]),
255                 viewFrame.size.height - kPagerPreferredHeight,
256                 [pagerControl_ bounds].size.width,
257                 kPagerPreferredHeight)];
258}
259
260- (void)selectedPageChanged:(int)newSelected {
261  [pagerControl_ selectSegmentWithTag:newSelected];
262}
263
264- (void)pageVisibilityChanged {
265  [pagerControl_ setNeedsDisplay:YES];
266}
267
268- (NSInteger)pagerSegmentAtLocation:(NSPoint)locationInWindow {
269  return [pagerControl_ findAndHighlightSegmentAtLocation:locationInWindow];
270}
271
272- (app_list::SearchBoxModel*)searchBoxModel {
273  app_list::AppListModel* appListModel = [appsGridController_ model];
274  return appListModel ? appListModel->search_box() : NULL;
275}
276
277- (app_list::AppListViewDelegate*)appListDelegate {
278  return [self delegate];
279}
280
281- (BOOL)control:(NSControl*)control
282               textView:(NSTextView*)textView
283    doCommandBySelector:(SEL)command {
284  if (showingSearchResults_)
285    return [appsSearchResultsController_ handleCommandBySelector:command];
286
287  // If anything has been written, let the search view handle it.
288  if ([[control stringValue] length] > 0)
289    return NO;
290
291  // Handle escape.
292  if (command == @selector(complete:) ||
293      command == @selector(cancel:) ||
294      command == @selector(cancelOperation:)) {
295    if (delegate_)
296      delegate_->Dismiss();
297    return YES;
298  }
299
300  // Possibly handle grid navigation.
301  return [appsGridController_ handleCommandBySelector:command];
302}
303
304- (void)modelTextDidChange {
305  app_list::SearchBoxModel* searchBoxModel = [self searchBoxModel];
306  if (!searchBoxModel || !delegate_)
307    return;
308
309  base::string16 query;
310  TrimWhitespace(searchBoxModel->text(), TRIM_ALL, &query);
311  BOOL shouldShowSearch = !query.empty();
312  [self revealSearchResults:shouldShowSearch];
313  if (shouldShowSearch)
314    delegate_->StartSearch();
315  else
316    delegate_->StopSearch();
317}
318
319- (app_list::AppListModel*)appListModel {
320  return [appsGridController_ model];
321}
322
323- (void)openResult:(app_list::SearchResult*)result {
324  if (delegate_)
325    delegate_->OpenSearchResult(result, 0 /* event flags */);
326}
327
328- (void)redoSearch {
329  [self modelTextDidChange];
330}
331
332- (void)onProfilesChanged {
333  [appsSearchBoxController_ rebuildMenu];
334  app_list::SigninDelegate* signinDelegate =
335      delegate_ ? delegate_->GetSigninDelegate() : NULL;
336  BOOL showSigninView = signinDelegate && signinDelegate->NeedSignin();
337
338  [[signinViewController_ view] removeFromSuperview];
339  signinViewController_.reset();
340
341  if (!showSigninView) {
342    [backgroundView_ setHidden:NO];
343    return;
344  }
345
346  [backgroundView_ setHidden:YES];
347  signinViewController_.reset(
348      [[SigninViewController alloc] initWithFrame:[backgroundView_ frame]
349                                     cornerRadius:kBubbleCornerRadius
350                                         delegate:signinDelegate]);
351  [[self view] addSubview:[signinViewController_ view]];
352}
353
354@end
355