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