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