| OLD | NEW |
| (Empty) |
| 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 <stddef.h> | |
| 8 | |
| 9 #include "base/mac/foundation_util.h" | |
| 10 #include "base/mac/mac_util.h" | |
| 11 #include "base/macros.h" | |
| 12 #include "base/strings/string_util.h" | |
| 13 #include "base/strings/sys_string_conversions.h" | |
| 14 #include "base/strings/utf_string_conversions.h" | |
| 15 #include "skia/ext/skia_utils_mac.h" | |
| 16 #include "ui/app_list/app_list_constants.h" | |
| 17 #include "ui/app_list/app_list_model.h" | |
| 18 #include "ui/app_list/app_list_view_delegate.h" | |
| 19 #include "ui/app_list/app_list_view_delegate_observer.h" | |
| 20 #import "ui/app_list/cocoa/app_list_pager_view.h" | |
| 21 #import "ui/app_list/cocoa/apps_grid_controller.h" | |
| 22 #include "ui/app_list/search_box_model.h" | |
| 23 #import "ui/base/cocoa/flipped_view.h" | |
| 24 #import "ui/gfx/image/image_skia_util_mac.h" | |
| 25 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" | |
| 26 | |
| 27 namespace { | |
| 28 | |
| 29 // The roundedness of the corners of the bubble. | |
| 30 const CGFloat kBubbleCornerRadius = 3; | |
| 31 | |
| 32 // Height of the pager. | |
| 33 const CGFloat kPagerPreferredHeight = 57; | |
| 34 | |
| 35 // Height of separator line drawn between the searchbox and grid view. | |
| 36 const CGFloat kTopSeparatorSize = 1; | |
| 37 | |
| 38 // Height of the search input. | |
| 39 const CGFloat kSearchInputHeight = 48; | |
| 40 | |
| 41 // Minimum margin on either side of the pager. If the pager grows beyond this, | |
| 42 // the segment size is reduced. | |
| 43 const CGFloat kMinPagerMargin = 40; | |
| 44 // Maximum width of a single segment. | |
| 45 const CGFloat kMaxSegmentWidth = 80; | |
| 46 | |
| 47 // Duration of the animation for sliding in and out search results. | |
| 48 const NSTimeInterval kResultsAnimationDuration = 0.2; | |
| 49 | |
| 50 // Properties of the message rectangle, if it is shown. | |
| 51 const NSRect kMessageRect = {{12, 12}, {370, 91}}; | |
| 52 const CGFloat kMessageCornerRadius = 2; | |
| 53 const CGFloat kSpacingBelowMessageTitle = 6; | |
| 54 const SkColor kMessageBackgroundColor = SkColorSetRGB(0xFF, 0xFD, 0xE7); | |
| 55 const SkColor kMessageStrokeColor = SkColorSetARGB(0x3d, 0x00, 0x00, 0x00); | |
| 56 // The inset should be 16px, but NSTextView has its own inset of 3. | |
| 57 const CGFloat kMessageTextInset = 13; | |
| 58 | |
| 59 } // namespace | |
| 60 | |
| 61 @interface BackgroundView : FlippedView; | |
| 62 @end | |
| 63 | |
| 64 @implementation BackgroundView | |
| 65 | |
| 66 - (void)drawRect:(NSRect)dirtyRect { | |
| 67 gfx::ScopedNSGraphicsContextSaveGState context; | |
| 68 NSRect boundsRect = [self bounds]; | |
| 69 NSRect searchAreaRect = NSMakeRect(0, 0, | |
| 70 NSWidth(boundsRect), kSearchInputHeight); | |
| 71 NSRect separatorRect = NSMakeRect(0, NSMaxY(searchAreaRect), | |
| 72 NSWidth(boundsRect), kTopSeparatorSize); | |
| 73 | |
| 74 [[NSBezierPath bezierPathWithRoundedRect:boundsRect | |
| 75 xRadius:kBubbleCornerRadius | |
| 76 yRadius:kBubbleCornerRadius] addClip]; | |
| 77 | |
| 78 [skia::SkColorToSRGBNSColor(app_list::kContentsBackgroundColor) set]; | |
| 79 NSRectFill(boundsRect); | |
| 80 [skia::SkColorToSRGBNSColor(app_list::kSearchBoxBackground) set]; | |
| 81 NSRectFill(searchAreaRect); | |
| 82 [skia::SkColorToSRGBNSColor(app_list::kTopSeparatorColor) set]; | |
| 83 NSRectFill(separatorRect); | |
| 84 } | |
| 85 | |
| 86 @end | |
| 87 | |
| 88 @interface MessageBackgroundView : FlippedView | |
| 89 @end | |
| 90 | |
| 91 @implementation MessageBackgroundView | |
| 92 | |
| 93 - (void)drawRect:(NSRect)dirtyRect { | |
| 94 NSRect boundsRect = [self bounds]; | |
| 95 gfx::ScopedNSGraphicsContextSaveGState context; | |
| 96 [[NSBezierPath bezierPathWithRoundedRect:boundsRect | |
| 97 xRadius:kMessageCornerRadius | |
| 98 yRadius:kMessageCornerRadius] addClip]; | |
| 99 | |
| 100 [skia::SkColorToSRGBNSColor(kMessageStrokeColor) set]; | |
| 101 NSRectFill(boundsRect); | |
| 102 | |
| 103 [[NSBezierPath bezierPathWithRoundedRect:NSInsetRect(boundsRect, 1, 1) | |
| 104 xRadius:kMessageCornerRadius | |
| 105 yRadius:kMessageCornerRadius] addClip]; | |
| 106 [skia::SkColorToSRGBNSColor(kMessageBackgroundColor) set]; | |
| 107 NSRectFill(boundsRect); | |
| 108 } | |
| 109 | |
| 110 @end | |
| 111 | |
| 112 @interface AppListViewController () | |
| 113 | |
| 114 - (void)updateMessage; | |
| 115 - (void)loadAndSetView; | |
| 116 - (void)revealSearchResults:(BOOL)show; | |
| 117 | |
| 118 @end | |
| 119 | |
| 120 namespace app_list { | |
| 121 | |
| 122 class AppListModelObserverBridge : public AppListViewDelegateObserver { | |
| 123 public: | |
| 124 AppListModelObserverBridge(AppListViewController* parent); | |
| 125 ~AppListModelObserverBridge() override; | |
| 126 | |
| 127 private: | |
| 128 // Overridden from app_list::AppListViewDelegateObserver: | |
| 129 void OnProfilesChanged() override; | |
| 130 void OnShutdown() override; | |
| 131 | |
| 132 AppListViewController* parent_; // Weak. Owns us. | |
| 133 | |
| 134 DISALLOW_COPY_AND_ASSIGN(AppListModelObserverBridge); | |
| 135 }; | |
| 136 | |
| 137 AppListModelObserverBridge::AppListModelObserverBridge( | |
| 138 AppListViewController* parent) | |
| 139 : parent_(parent) { | |
| 140 [parent_ delegate]->AddObserver(this); | |
| 141 } | |
| 142 | |
| 143 AppListModelObserverBridge::~AppListModelObserverBridge() { | |
| 144 [parent_ delegate]->RemoveObserver(this); | |
| 145 } | |
| 146 | |
| 147 void AppListModelObserverBridge::OnProfilesChanged() { | |
| 148 [parent_ onProfilesChanged]; | |
| 149 } | |
| 150 | |
| 151 void AppListModelObserverBridge::OnShutdown() { | |
| 152 [parent_ setDelegate:nil]; | |
| 153 } | |
| 154 | |
| 155 } // namespace app_list | |
| 156 | |
| 157 @implementation AppListViewController | |
| 158 | |
| 159 - (id)init { | |
| 160 if ((self = [super init])) { | |
| 161 appsGridController_.reset([[AppsGridController alloc] init]); | |
| 162 [self loadAndSetView]; | |
| 163 | |
| 164 [self totalPagesChanged]; | |
| 165 [self selectedPageChanged:0]; | |
| 166 [appsGridController_ setPaginationObserver:self]; | |
| 167 } | |
| 168 return self; | |
| 169 } | |
| 170 | |
| 171 - (void)dealloc { | |
| 172 // Ensure that setDelegate(NULL) has been called before destruction, because | |
| 173 // dealloc can be called at odd times, and Objective C destruction order does | |
| 174 // not properly tear down these dependencies. | |
| 175 DCHECK(delegate_ == NULL); | |
| 176 [appsGridController_ setPaginationObserver:nil]; | |
| 177 [super dealloc]; | |
| 178 } | |
| 179 | |
| 180 - (AppsSearchBoxController*)searchBoxController { | |
| 181 return appsSearchBoxController_; | |
| 182 } | |
| 183 | |
| 184 - (BOOL)showingSearchResults { | |
| 185 return showingSearchResults_; | |
| 186 } | |
| 187 | |
| 188 - (AppsGridController*)appsGridController { | |
| 189 return appsGridController_; | |
| 190 } | |
| 191 | |
| 192 - (NSSegmentedControl*)pagerControl { | |
| 193 return pagerControl_; | |
| 194 } | |
| 195 | |
| 196 - (NSView*)backgroundView { | |
| 197 return backgroundView_; | |
| 198 } | |
| 199 | |
| 200 - (app_list::AppListViewDelegate*)delegate { | |
| 201 return delegate_; | |
| 202 } | |
| 203 | |
| 204 - (void)setDelegate:(app_list::AppListViewDelegate*)newDelegate { | |
| 205 if (delegate_) { | |
| 206 // Ensure the search box is cleared when switching profiles. | |
| 207 if ([self searchBoxModel]) | |
| 208 [self searchBoxModel]->SetText(base::string16()); | |
| 209 | |
| 210 // First clean up, in reverse order. | |
| 211 app_list_model_observer_bridge_.reset(); | |
| 212 [appsSearchResultsController_ setDelegate:nil]; | |
| 213 [appsSearchBoxController_ setDelegate:nil]; | |
| 214 [appsGridController_ setDelegate:nil]; | |
| 215 [messageText_ setDelegate:nil]; | |
| 216 } | |
| 217 delegate_ = newDelegate; | |
| 218 if (delegate_) { | |
| 219 [loadingIndicator_ stopAnimation:self]; | |
| 220 } else { | |
| 221 [loadingIndicator_ startAnimation:self]; | |
| 222 return; | |
| 223 } | |
| 224 | |
| 225 [appsGridController_ setDelegate:delegate_]; | |
| 226 [appsSearchBoxController_ setDelegate:self]; | |
| 227 [appsSearchResultsController_ setDelegate:self]; | |
| 228 app_list_model_observer_bridge_.reset( | |
| 229 new app_list::AppListModelObserverBridge(self)); | |
| 230 [self onProfilesChanged]; | |
| 231 [self updateMessage]; | |
| 232 } | |
| 233 | |
| 234 - (void)updateMessage { | |
| 235 if (![AppsGridController hasFewerRows]) | |
| 236 return; | |
| 237 | |
| 238 ResourceBundle& rb = ResourceBundle::GetSharedInstance(); | |
| 239 NSFont* messageFont = rb.GetFontWithDelta(0).GetNativeFont(); | |
| 240 NSFont* titleFont = rb.GetFontWithDelta(2).GetNativeFont(); | |
| 241 | |
| 242 base::string16 title = delegate_->GetMessageTitle(); | |
| 243 size_t messageBreak; | |
| 244 base::string16 messageFull = delegate_->GetMessageText(&messageBreak); | |
| 245 base::string16 shortcutName = delegate_->GetAppsShortcutName(); | |
| 246 base::string16 learnMore = delegate_->GetLearnMoreText(); | |
| 247 base::string16 learnMoreUrl = delegate_->GetLearnMoreLink(); | |
| 248 | |
| 249 base::string16 messagePre = messageFull.substr(0, messageBreak); | |
| 250 base::string16 messagePost = messageFull.substr(messageBreak); | |
| 251 | |
| 252 NSURL* linkURL = [NSURL URLWithString:base::SysUTF16ToNSString(learnMoreUrl)]; | |
| 253 gfx::ImageSkia* icon = delegate_->GetAppsIcon(); | |
| 254 | |
| 255 // Shift the baseline up so that the graphics align centered. 4 looks nice. It | |
| 256 // happens to be the image size minus the font size, but that's a coincidence. | |
| 257 const CGFloat kBaselineShift = 4; | |
| 258 base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle( | |
| 259 [[NSMutableParagraphStyle alloc] init]); | |
| 260 [paragraphStyle setLineSpacing:kSpacingBelowMessageTitle + kBaselineShift]; | |
| 261 | |
| 262 NSNumber* baselineOffset = [NSNumber numberWithFloat:kBaselineShift]; | |
| 263 base::scoped_nsobject<NSMutableAttributedString> text( | |
| 264 [[NSMutableAttributedString alloc] | |
| 265 initWithString:base::SysUTF16ToNSString(title) | |
| 266 attributes:@{ | |
| 267 NSParagraphStyleAttributeName : paragraphStyle, | |
| 268 NSFontAttributeName : titleFont | |
| 269 }]); | |
| 270 | |
| 271 NSDictionary* defaultAttributes = @{ | |
| 272 NSFontAttributeName : messageFont, | |
| 273 NSBaselineOffsetAttributeName : baselineOffset | |
| 274 }; | |
| 275 | |
| 276 base::scoped_nsobject<NSAttributedString> lineBreak( | |
| 277 [[NSAttributedString alloc] initWithString:@"\n" | |
| 278 attributes:defaultAttributes]); | |
| 279 base::scoped_nsobject<NSAttributedString> space([[NSAttributedString alloc] | |
| 280 initWithString:@" " | |
| 281 attributes:defaultAttributes]); | |
| 282 base::scoped_nsobject<NSAttributedString> messagePreString( | |
| 283 [[NSAttributedString alloc] | |
| 284 initWithString:base::SysUTF16ToNSString(messagePre) | |
| 285 attributes:defaultAttributes]); | |
| 286 base::scoped_nsobject<NSAttributedString> messagePostString( | |
| 287 [[NSAttributedString alloc] | |
| 288 initWithString:base::SysUTF16ToNSString(messagePost) | |
| 289 attributes:defaultAttributes]); | |
| 290 | |
| 291 // NSUnderlineStyleNone is broken. | |
| 292 base::scoped_nsobject<NSAttributedString> learnMoreString( | |
| 293 [[NSAttributedString alloc] | |
| 294 initWithString:base::SysUTF16ToNSString(learnMore) | |
| 295 attributes:@{ | |
| 296 NSParagraphStyleAttributeName : paragraphStyle, | |
| 297 NSFontAttributeName : messageFont, | |
| 298 NSLinkAttributeName : linkURL, | |
| 299 NSBaselineOffsetAttributeName : baselineOffset, | |
| 300 NSUnderlineStyleAttributeName : | |
| 301 [NSNumber numberWithInt:NSUnderlineStyleNone] | |
| 302 }]); | |
| 303 base::scoped_nsobject<NSAttributedString> shortcutStringText( | |
| 304 [[NSAttributedString alloc] | |
| 305 initWithString:base::SysUTF16ToNSString(shortcutName) | |
| 306 attributes:defaultAttributes]); | |
| 307 base::scoped_nsobject<NSMutableAttributedString> shortcutString( | |
| 308 [[NSMutableAttributedString alloc] init]); | |
| 309 if (icon) { | |
| 310 NSImage* image = gfx::NSImageFromImageSkia(*icon); | |
| 311 // The image has a bunch of representations. Ensure the smallest is used. | |
| 312 // (Going smaller would make pixels all manky, so don't do that). | |
| 313 [image setSize:NSMakeSize(16, 16)]; | |
| 314 | |
| 315 base::scoped_nsobject<NSTextAttachmentCell> attachmentCell( | |
| 316 [[NSTextAttachmentCell alloc] initImageCell:image]); | |
| 317 base::scoped_nsobject<NSTextAttachment> attachment( | |
| 318 [[NSTextAttachment alloc] init]); | |
| 319 [attachment setAttachmentCell:attachmentCell]; | |
| 320 [shortcutString | |
| 321 appendAttributedString:[NSAttributedString | |
| 322 attributedStringWithAttachment:attachment]]; | |
| 323 [shortcutString appendAttributedString:space]; | |
| 324 } | |
| 325 [shortcutString appendAttributedString:shortcutStringText]; | |
| 326 | |
| 327 [text appendAttributedString:lineBreak]; | |
| 328 [text appendAttributedString:messagePreString]; | |
| 329 [text appendAttributedString:shortcutString]; | |
| 330 [text appendAttributedString:messagePostString]; | |
| 331 [text appendAttributedString:space]; | |
| 332 [text appendAttributedString:learnMoreString]; | |
| 333 | |
| 334 [[messageText_ textStorage] setAttributedString:text]; | |
| 335 [messageText_ sizeToFit]; | |
| 336 | |
| 337 // If the user scroller preference is to always show scrollbars, and the | |
| 338 // translated message is long, the scroll track may be present. This means | |
| 339 // text will be under the scroller. We only want vertical scrolling, but | |
| 340 // reducing the width puts the scroll track in a weird spot. So, increase the | |
| 341 // width of the scroll view to move the track into the padding towards the | |
| 342 // message background border, then reduce the width of the text view. The | |
| 343 // non-overlay scroller still looks kinda weird but hopefully not many will | |
| 344 // actually see it. | |
| 345 CGFloat overlap = | |
| 346 NSWidth([messageText_ bounds]) - [messageScrollView_ contentSize].width; | |
| 347 if (overlap > 0) { | |
| 348 NSRect rect = [messageScrollView_ frame]; | |
| 349 rect.size.width += kMessageTextInset - 2; | |
| 350 [messageScrollView_ setFrame:rect]; | |
| 351 overlap -= kMessageTextInset - 2; | |
| 352 DCHECK_GT(overlap, 0); | |
| 353 rect = [messageText_ frame]; | |
| 354 rect.size.width -= overlap; | |
| 355 [messageText_ setFrame:rect]; | |
| 356 [messageText_ sizeToFit]; | |
| 357 | |
| 358 // And after doing all that for some reason Cocoa scrolls to the bottom. So | |
| 359 // fix that. | |
| 360 [[messageScrollView_ documentView] scrollPoint:NSMakePoint(0, 0)]; | |
| 361 } | |
| 362 | |
| 363 [messageText_ setDelegate:self]; | |
| 364 } | |
| 365 | |
| 366 - (void)loadAndSetView { | |
| 367 pagerControl_.reset([[AppListPagerView alloc] init]); | |
| 368 [pagerControl_ setTarget:appsGridController_]; | |
| 369 [pagerControl_ setAction:@selector(onPagerClicked:)]; | |
| 370 | |
| 371 NSRect gridFrame = [[appsGridController_ view] frame]; | |
| 372 | |
| 373 base::scoped_nsobject<NSView> messageTextBackground; | |
| 374 if ([AppsGridController hasFewerRows]) { | |
| 375 messageTextBackground.reset( | |
| 376 [[MessageBackgroundView alloc] initWithFrame:kMessageRect]); | |
| 377 NSRect frameRect = | |
| 378 NSInsetRect(kMessageRect, kMessageTextInset, kMessageTextInset); | |
| 379 messageText_.reset([[NSTextView alloc] initWithFrame:frameRect]); | |
| 380 // Provide a solid background here (as well as the background) so that | |
| 381 // subpixel AA works. | |
| 382 [messageText_ | |
| 383 setBackgroundColor:skia::SkColorToSRGBNSColor(kMessageBackgroundColor)]; | |
| 384 [messageText_ setDrawsBackground:YES]; | |
| 385 [messageText_ setEditable:NO]; | |
| 386 // Ideally setSelectable:NO would also be set here, but that disables mouse | |
| 387 // events completely, breaking the "Learn more" link. Instead, selection is | |
| 388 // "disabled" via a delegate method which Apple's documentation suggests. In | |
| 389 // reality, selection still happens, it just disappears once the mouse is | |
| 390 // released. To avoid the selection appearing, also set selected text to | |
| 391 // have no special attributes. Sadly, the mouse cursor still displays an | |
| 392 // I-beam, but hacking cursor rectangles on the view so that the "Learn | |
| 393 // More" link is still correctly handled is too hard. | |
| 394 [messageText_ setSelectedTextAttributes:@{}]; | |
| 395 gridFrame.origin.y += NSMaxY([messageTextBackground frame]); | |
| 396 } | |
| 397 | |
| 398 [[appsGridController_ view] setFrame:gridFrame]; | |
| 399 | |
| 400 NSRect contentsRect = | |
| 401 NSMakeRect(0, kSearchInputHeight + kTopSeparatorSize, NSWidth(gridFrame), | |
| 402 NSMaxY(gridFrame) + kPagerPreferredHeight - | |
| 403 [AppsGridController scrollerPadding]); | |
| 404 | |
| 405 contentsView_.reset([[FlippedView alloc] initWithFrame:contentsRect]); | |
| 406 | |
| 407 // The contents view contains animations both from an NSCollectionView and the | |
| 408 // app list's own transitive drag layers. On Mavericks, the subviews need to | |
| 409 // have access to a compositing layer they can share. Otherwise the compositor | |
| 410 // makes tearing artifacts. However, doing this on Mountain Lion or earler | |
| 411 // results in flickering whilst an item is installing. | |
| 412 if (base::mac::IsOSMavericksOrLater()) | |
| 413 [contentsView_ setWantsLayer:YES]; | |
| 414 | |
| 415 backgroundView_.reset( | |
| 416 [[BackgroundView alloc] initWithFrame: | |
| 417 NSMakeRect(0, 0, NSMaxX(contentsRect), NSMaxY(contentsRect))]); | |
| 418 appsSearchBoxController_.reset( | |
| 419 [[AppsSearchBoxController alloc] initWithFrame: | |
| 420 NSMakeRect(0, 0, NSWidth(contentsRect), kSearchInputHeight)]); | |
| 421 appsSearchResultsController_.reset( | |
| 422 [[AppsSearchResultsController alloc] initWithAppsSearchResultsFrameSize: | |
| 423 [contentsView_ bounds].size]); | |
| 424 base::scoped_nsobject<NSView> containerView( | |
| 425 [[NSView alloc] initWithFrame:[backgroundView_ frame]]); | |
| 426 | |
| 427 loadingIndicator_.reset( | |
| 428 [[NSProgressIndicator alloc] initWithFrame:NSZeroRect]); | |
| 429 [loadingIndicator_ setStyle:NSProgressIndicatorSpinningStyle]; | |
| 430 [loadingIndicator_ sizeToFit]; | |
| 431 NSRect indicatorRect = [loadingIndicator_ frame]; | |
| 432 indicatorRect.origin.x = NSWidth(contentsRect) / 2 - NSMidX(indicatorRect); | |
| 433 indicatorRect.origin.y = NSHeight(contentsRect) / 2 - NSMidY(indicatorRect); | |
| 434 [loadingIndicator_ setFrame:indicatorRect]; | |
| 435 [loadingIndicator_ setDisplayedWhenStopped:NO]; | |
| 436 [loadingIndicator_ startAnimation:self]; | |
| 437 | |
| 438 if (messageText_) { | |
| 439 [contentsView_ addSubview:messageTextBackground]; | |
| 440 | |
| 441 // Add a scroll view in case the translation is long and doesn't fit. Mac | |
| 442 // likes to hide scrollbars, so add to the height so the user can see part | |
| 443 // of the next line of text: just extend out into the padding towards the | |
| 444 // text background's border. Subtract at least 2: one for the border stroke | |
| 445 // and one for a bit of padding. | |
| 446 NSRect frameRect = [messageText_ frame]; | |
| 447 frameRect.size.height += kMessageTextInset - 2; | |
| 448 messageScrollView_.reset([[NSScrollView alloc] initWithFrame:frameRect]); | |
| 449 [messageScrollView_ setHasVerticalScroller:YES]; | |
| 450 [messageScrollView_ setAutohidesScrollers:YES]; | |
| 451 | |
| 452 // Now the message is going into an NSScrollView, origin should be 0, 0. | |
| 453 frameRect = [messageText_ frame]; | |
| 454 frameRect.origin = NSMakePoint(0, 0); | |
| 455 [messageText_ setFrame:frameRect]; | |
| 456 | |
| 457 [messageScrollView_ setDocumentView:messageText_]; | |
| 458 [contentsView_ addSubview:messageScrollView_]; | |
| 459 } | |
| 460 [contentsView_ addSubview:[appsGridController_ view]]; | |
| 461 [contentsView_ addSubview:pagerControl_]; | |
| 462 [contentsView_ addSubview:loadingIndicator_]; | |
| 463 [backgroundView_ addSubview:contentsView_]; | |
| 464 [backgroundView_ addSubview:[appsSearchResultsController_ view]]; | |
| 465 [backgroundView_ addSubview:[appsSearchBoxController_ view]]; | |
| 466 [containerView addSubview:backgroundView_]; | |
| 467 [self setView:containerView]; | |
| 468 } | |
| 469 | |
| 470 - (void)revealSearchResults:(BOOL)show { | |
| 471 if (show == showingSearchResults_) | |
| 472 return; | |
| 473 | |
| 474 showingSearchResults_ = show; | |
| 475 NSSize contentsSize = [contentsView_ frame].size; | |
| 476 NSRect resultsTargetRect = NSMakeRect( | |
| 477 0, kSearchInputHeight + kTopSeparatorSize, | |
| 478 contentsSize.width, contentsSize.height); | |
| 479 NSRect contentsTargetRect = resultsTargetRect; | |
| 480 | |
| 481 // Shows results by sliding the grid and pager down to the bottom of the view. | |
| 482 // Hides results by collapsing the search results container to a height of 0. | |
| 483 if (show) | |
| 484 contentsTargetRect.origin.y += NSHeight(contentsTargetRect); | |
| 485 else | |
| 486 resultsTargetRect.size.height = 0; | |
| 487 | |
| 488 [[NSAnimationContext currentContext] setDuration:kResultsAnimationDuration]; | |
| 489 [[contentsView_ animator] setFrame:contentsTargetRect]; | |
| 490 [[[appsSearchResultsController_ view] animator] setFrame:resultsTargetRect]; | |
| 491 } | |
| 492 | |
| 493 - (void)totalPagesChanged { | |
| 494 size_t pageCount = [appsGridController_ pageCount]; | |
| 495 [pagerControl_ setSegmentCount:pageCount]; | |
| 496 | |
| 497 NSRect viewFrame = [[pagerControl_ superview] bounds]; | |
| 498 CGFloat segmentWidth = std::min( | |
| 499 kMaxSegmentWidth, | |
| 500 (viewFrame.size.width - 2 * kMinPagerMargin) / pageCount); | |
| 501 | |
| 502 for (size_t i = 0; i < pageCount; ++i) { | |
| 503 [pagerControl_ setWidth:segmentWidth | |
| 504 forSegment:i]; | |
| 505 [[pagerControl_ cell] setTag:i | |
| 506 forSegment:i]; | |
| 507 } | |
| 508 | |
| 509 // Center in view. | |
| 510 [pagerControl_ sizeToFit]; | |
| 511 [pagerControl_ setFrame: | |
| 512 NSMakeRect(NSMidX(viewFrame) - NSMidX([pagerControl_ bounds]), | |
| 513 viewFrame.size.height - kPagerPreferredHeight, | |
| 514 [pagerControl_ bounds].size.width, | |
| 515 kPagerPreferredHeight)]; | |
| 516 } | |
| 517 | |
| 518 - (void)selectedPageChanged:(int)newSelected { | |
| 519 [pagerControl_ selectSegmentWithTag:newSelected]; | |
| 520 } | |
| 521 | |
| 522 - (void)pageVisibilityChanged { | |
| 523 [pagerControl_ setNeedsDisplay:YES]; | |
| 524 } | |
| 525 | |
| 526 - (NSInteger)pagerSegmentAtLocation:(NSPoint)locationInWindow { | |
| 527 return [pagerControl_ findAndHighlightSegmentAtLocation:locationInWindow]; | |
| 528 } | |
| 529 | |
| 530 - (app_list::SearchBoxModel*)searchBoxModel { | |
| 531 app_list::AppListModel* appListModel = [appsGridController_ model]; | |
| 532 return appListModel ? appListModel->search_box() : NULL; | |
| 533 } | |
| 534 | |
| 535 - (app_list::AppListViewDelegate*)appListDelegate { | |
| 536 return [self delegate]; | |
| 537 } | |
| 538 | |
| 539 - (BOOL)control:(NSControl*)control | |
| 540 textView:(NSTextView*)textView | |
| 541 doCommandBySelector:(SEL)command { | |
| 542 if (showingSearchResults_) | |
| 543 return [appsSearchResultsController_ handleCommandBySelector:command]; | |
| 544 | |
| 545 // If anything has been written, let the search view handle it. | |
| 546 if ([[control stringValue] length] > 0) | |
| 547 return NO; | |
| 548 | |
| 549 // Handle escape. | |
| 550 if (command == @selector(complete:) || | |
| 551 command == @selector(cancel:) || | |
| 552 command == @selector(cancelOperation:)) { | |
| 553 if (delegate_) | |
| 554 delegate_->Dismiss(); | |
| 555 return YES; | |
| 556 } | |
| 557 | |
| 558 // Possibly handle grid navigation. | |
| 559 return [appsGridController_ handleCommandBySelector:command]; | |
| 560 } | |
| 561 | |
| 562 - (void)modelTextDidChange { | |
| 563 app_list::SearchBoxModel* searchBoxModel = [self searchBoxModel]; | |
| 564 if (!searchBoxModel || !delegate_) | |
| 565 return; | |
| 566 | |
| 567 base::string16 query; | |
| 568 base::TrimWhitespace(searchBoxModel->text(), base::TRIM_ALL, &query); | |
| 569 BOOL shouldShowSearch = !query.empty(); | |
| 570 [self revealSearchResults:shouldShowSearch]; | |
| 571 if (shouldShowSearch) | |
| 572 delegate_->StartSearch(); | |
| 573 else | |
| 574 delegate_->StopSearch(); | |
| 575 } | |
| 576 | |
| 577 - (app_list::AppListModel*)appListModel { | |
| 578 return [appsGridController_ model]; | |
| 579 } | |
| 580 | |
| 581 - (void)openResult:(app_list::SearchResult*)result { | |
| 582 if (delegate_) { | |
| 583 delegate_->OpenSearchResult( | |
| 584 result, false /* auto_launch */, 0 /* event flags */); | |
| 585 } | |
| 586 } | |
| 587 | |
| 588 - (void)onProfilesChanged { | |
| 589 [appsSearchBoxController_ rebuildMenu]; | |
| 590 } | |
| 591 | |
| 592 // NSTextViewDelegate implementation. | |
| 593 | |
| 594 - (BOOL)textView:(NSTextView*)textView | |
| 595 clickedOnLink:(id)link | |
| 596 atIndex:(NSUInteger)charIndex { | |
| 597 DCHECK(delegate_); | |
| 598 delegate_->OpenLearnMoreLink(); | |
| 599 return YES; | |
| 600 } | |
| 601 | |
| 602 - (NSArray*)textView:(NSTextView*)aTextView | |
| 603 willChangeSelectionFromCharacterRanges:(NSArray*)oldSelectedCharRanges | |
| 604 toCharacterRanges:(NSArray*)newSelectedCharRanges { | |
| 605 return oldSelectedCharRanges; | |
| 606 } | |
| 607 | |
| 608 @end | |
| OLD | NEW |