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