Upstream version 5.34.104.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/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
23 namespace {
24
25 // The roundedness of the corners of the bubble.
26 const CGFloat kBubbleCornerRadius = 3;
27
28 // Height of the pager.
29 const CGFloat kPagerPreferredHeight = 57;
30
31 // Height of separator line drawn between the searchbox and grid view.
32 const CGFloat kTopSeparatorSize = 1;
33
34 // Height of the search input.
35 const 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.
39 const CGFloat kMinPagerMargin = 40;
40 // Maximum width of a single segment.
41 const CGFloat kMaxSegmentWidth = 80;
42
43 // Duration of the animation for sliding in and out search results.
44 const 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
82 namespace app_list {
83
84 class 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
98 AppListModelObserverBridge::AppListModelObserverBridge(
99     AppListViewController* parent)
100     : parent_(parent) {
101   [parent_ delegate]->AddObserver(this);
102 }
103
104 AppListModelObserverBridge::~AppListModelObserverBridge() {
105   [parent_ delegate]->RemoveObserver(this);
106 }
107
108 void 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     // Ensure the search box is cleared when switching profiles.
164     if ([self searchBoxModel])
165       [self searchBoxModel]->SetText(base::string16());
166
167     // First clean up, in reverse order.
168     app_list_model_observer_bridge_.reset();
169     [appsSearchResultsController_ setDelegate:nil];
170     [appsSearchBoxController_ setDelegate:nil];
171     [appsGridController_ setDelegate:nil];
172   }
173   delegate_.reset(newDelegate.release());
174   if (delegate_) {
175     [loadingIndicator_ stopAnimation:self];
176   } else {
177     [loadingIndicator_ startAnimation:self];
178     return;
179   }
180
181   [appsGridController_ setDelegate:delegate_.get()];
182   [appsSearchBoxController_ setDelegate:self];
183   [appsSearchResultsController_ setDelegate:self];
184   app_list_model_observer_bridge_.reset(
185       new app_list::AppListModelObserverBridge(self));
186   [self onProfilesChanged];
187 }
188
189 -(void)loadAndSetView {
190   pagerControl_.reset([[AppListPagerView alloc] init]);
191   [pagerControl_ setTarget:appsGridController_];
192   [pagerControl_ setAction:@selector(onPagerClicked:)];
193
194   NSRect gridFrame = [[appsGridController_ view] frame];
195   NSRect contentsRect = NSMakeRect(0, kSearchInputHeight + kTopSeparatorSize,
196       NSWidth(gridFrame), NSHeight(gridFrame) + kPagerPreferredHeight -
197           [AppsGridController scrollerPadding]);
198
199   contentsView_.reset([[FlippedView alloc] initWithFrame:contentsRect]);
200
201   // The contents view contains animations both from an NSCollectionView and the
202   // app list's own transitive drag layers. Ensure the subviews have access to
203   // a compositing layer they can share.
204   [contentsView_ setWantsLayer:YES];
205
206   backgroundView_.reset(
207       [[BackgroundView alloc] initWithFrame:
208               NSMakeRect(0, 0, NSMaxX(contentsRect), NSMaxY(contentsRect))]);
209   appsSearchBoxController_.reset(
210       [[AppsSearchBoxController alloc] initWithFrame:
211           NSMakeRect(0, 0, NSWidth(contentsRect), kSearchInputHeight)]);
212   appsSearchResultsController_.reset(
213       [[AppsSearchResultsController alloc] initWithAppsSearchResultsFrameSize:
214           [contentsView_ bounds].size]);
215   base::scoped_nsobject<NSView> containerView(
216       [[NSView alloc] initWithFrame:[backgroundView_ frame]]);
217
218   loadingIndicator_.reset(
219       [[NSProgressIndicator alloc] initWithFrame:NSZeroRect]);
220   [loadingIndicator_ setStyle:NSProgressIndicatorSpinningStyle];
221   [loadingIndicator_ sizeToFit];
222   NSRect indicatorRect = [loadingIndicator_ frame];
223   indicatorRect.origin.x = NSWidth(contentsRect) / 2 - NSMidX(indicatorRect);
224   indicatorRect.origin.y = NSHeight(contentsRect) / 2 - NSMidY(indicatorRect);
225   [loadingIndicator_ setFrame:indicatorRect];
226   [loadingIndicator_ setDisplayedWhenStopped:NO];
227   [loadingIndicator_ startAnimation:self];
228
229   [contentsView_ addSubview:[appsGridController_ view]];
230   [contentsView_ addSubview:pagerControl_];
231   [contentsView_ addSubview:loadingIndicator_];
232   [backgroundView_ addSubview:contentsView_];
233   [backgroundView_ addSubview:[appsSearchResultsController_ view]];
234   [backgroundView_ addSubview:[appsSearchBoxController_ view]];
235   [containerView addSubview:backgroundView_];
236   [self setView:containerView];
237 }
238
239 - (void)revealSearchResults:(BOOL)show {
240   if (show == showingSearchResults_)
241     return;
242
243   showingSearchResults_ = show;
244   NSSize contentsSize = [contentsView_ frame].size;
245   NSRect resultsTargetRect = NSMakeRect(
246       0, kSearchInputHeight + kTopSeparatorSize,
247       contentsSize.width, contentsSize.height);
248   NSRect contentsTargetRect = resultsTargetRect;
249
250   // Shows results by sliding the grid and pager down to the bottom of the view.
251   // Hides results by collapsing the search results container to a height of 0.
252   if (show)
253     contentsTargetRect.origin.y += NSHeight(contentsTargetRect);
254   else
255     resultsTargetRect.size.height = 0;
256
257   [[NSAnimationContext currentContext] setDuration:kResultsAnimationDuration];
258   [[contentsView_ animator] setFrame:contentsTargetRect];
259   [[[appsSearchResultsController_ view] animator] setFrame:resultsTargetRect];
260 }
261
262 - (void)totalPagesChanged {
263   size_t pageCount = [appsGridController_ pageCount];
264   [pagerControl_ setSegmentCount:pageCount];
265
266   NSRect viewFrame = [[pagerControl_ superview] bounds];
267   CGFloat segmentWidth = std::min(
268       kMaxSegmentWidth,
269       (viewFrame.size.width - 2 * kMinPagerMargin) / pageCount);
270
271   for (size_t i = 0; i < pageCount; ++i) {
272     [pagerControl_ setWidth:segmentWidth
273                  forSegment:i];
274     [[pagerControl_ cell] setTag:i
275                       forSegment:i];
276   }
277
278   // Center in view.
279   [pagerControl_ sizeToFit];
280   [pagerControl_ setFrame:
281       NSMakeRect(NSMidX(viewFrame) - NSMidX([pagerControl_ bounds]),
282                  viewFrame.size.height - kPagerPreferredHeight,
283                  [pagerControl_ bounds].size.width,
284                  kPagerPreferredHeight)];
285 }
286
287 - (void)selectedPageChanged:(int)newSelected {
288   [pagerControl_ selectSegmentWithTag:newSelected];
289 }
290
291 - (void)pageVisibilityChanged {
292   [pagerControl_ setNeedsDisplay:YES];
293 }
294
295 - (NSInteger)pagerSegmentAtLocation:(NSPoint)locationInWindow {
296   return [pagerControl_ findAndHighlightSegmentAtLocation:locationInWindow];
297 }
298
299 - (app_list::SearchBoxModel*)searchBoxModel {
300   app_list::AppListModel* appListModel = [appsGridController_ model];
301   return appListModel ? appListModel->search_box() : NULL;
302 }
303
304 - (app_list::AppListViewDelegate*)appListDelegate {
305   return [self delegate];
306 }
307
308 - (BOOL)control:(NSControl*)control
309                textView:(NSTextView*)textView
310     doCommandBySelector:(SEL)command {
311   if (showingSearchResults_)
312     return [appsSearchResultsController_ handleCommandBySelector:command];
313
314   // If anything has been written, let the search view handle it.
315   if ([[control stringValue] length] > 0)
316     return NO;
317
318   // Handle escape.
319   if (command == @selector(complete:) ||
320       command == @selector(cancel:) ||
321       command == @selector(cancelOperation:)) {
322     if (delegate_)
323       delegate_->Dismiss();
324     return YES;
325   }
326
327   // Possibly handle grid navigation.
328   return [appsGridController_ handleCommandBySelector:command];
329 }
330
331 - (void)modelTextDidChange {
332   app_list::SearchBoxModel* searchBoxModel = [self searchBoxModel];
333   if (!searchBoxModel || !delegate_)
334     return;
335
336   base::string16 query;
337   TrimWhitespace(searchBoxModel->text(), TRIM_ALL, &query);
338   BOOL shouldShowSearch = !query.empty();
339   [self revealSearchResults:shouldShowSearch];
340   if (shouldShowSearch)
341     delegate_->StartSearch();
342   else
343     delegate_->StopSearch();
344 }
345
346 - (app_list::AppListModel*)appListModel {
347   return [appsGridController_ model];
348 }
349
350 - (void)openResult:(app_list::SearchResult*)result {
351   if (delegate_) {
352     delegate_->OpenSearchResult(
353         result, false /* auto_launch */, 0 /* event flags */);
354   }
355 }
356
357 - (void)redoSearch {
358   [self modelTextDidChange];
359 }
360
361 - (void)onProfilesChanged {
362   [appsSearchBoxController_ rebuildMenu];
363   app_list::SigninDelegate* signinDelegate =
364       delegate_ ? delegate_->GetSigninDelegate() : NULL;
365   BOOL showSigninView = signinDelegate && signinDelegate->NeedSignin();
366
367   [[signinViewController_ view] removeFromSuperview];
368   signinViewController_.reset();
369
370   if (!showSigninView) {
371     [backgroundView_ setHidden:NO];
372     return;
373   }
374
375   [backgroundView_ setHidden:YES];
376   signinViewController_.reset(
377       [[SigninViewController alloc] initWithFrame:[backgroundView_ frame]
378                                      cornerRadius:kBubbleCornerRadius
379                                          delegate:signinDelegate]);
380   [[self view] addSubview:[signinViewController_ view]];
381 }
382
383 @end