1// Copyright (c) 2011 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 "chrome/browser/ui/cocoa/browser_window_controller_private.h" 6 7#include "base/command_line.h" 8#import "base/memory/scoped_nsobject.h" 9#include "chrome/browser/browser_process.h" 10#include "chrome/browser/prefs/pref_service.h" 11#include "chrome/browser/prefs/scoped_user_pref_update.h" 12#include "chrome/browser/profiles/profile.h" 13#include "chrome/browser/ui/browser_list.h" 14#import "chrome/browser/ui/cocoa/fast_resize_view.h" 15#import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h" 16#import "chrome/browser/ui/cocoa/floating_bar_backing_view.h" 17#import "chrome/browser/ui/cocoa/framed_browser_window.h" 18#import "chrome/browser/ui/cocoa/fullscreen_controller.h" 19#import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h" 20#import "chrome/browser/ui/cocoa/tab_contents/previewable_contents_controller.h" 21#import "chrome/browser/ui/cocoa/tabs/side_tab_strip_controller.h" 22#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h" 23#import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h" 24#import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" 25#include "chrome/common/chrome_switches.h" 26#include "chrome/common/pref_names.h" 27#include "content/browser/renderer_host/render_widget_host_view.h" 28#include "content/browser/tab_contents/tab_contents.h" 29#include "content/browser/tab_contents/tab_contents_view.h" 30 31// Provide the forward-declarations of new 10.7 SDK symbols so they can be 32// called when building with the 10.5 SDK. 33#if !defined(MAC_OS_X_VERSION_10_7) || \ 34 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 35 36@interface NSWindow (LionSDKDeclarations) 37- (void)toggleFullScreen:(id)sender; 38@end 39 40enum { 41 NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7, 42 NSWindowCollectionBehaviorFullScreenAuxiliary = 1 << 8 43}; 44 45enum { 46 NSWindowFullScreenButton = 7 47}; 48 49#endif // MAC_OS_X_VERSION_10_7 50 51namespace { 52 53// Space between the incognito badge and the right edge of the window. 54const CGFloat kIncognitoBadgeOffset = 4; 55 56// Insets for the location bar, used when the full toolbar is hidden. 57// TODO(viettrungluu): We can argue about the "correct" insetting; I like the 58// following best, though arguably 0 inset is better/more correct. 59const CGFloat kLocBarLeftRightInset = 1; 60const CGFloat kLocBarTopInset = 0; 61const CGFloat kLocBarBottomInset = 1; 62 63} // namespace 64 65@implementation BrowserWindowController(Private) 66 67// Create the appropriate tab strip controller based on whether or not side 68// tabs are enabled. 69- (void)createTabStripController { 70 Class factory = [TabStripController class]; 71 if ([self useVerticalTabs]) 72 factory = [SideTabStripController class]; 73 74 DCHECK([previewableContentsController_ activeContainer]); 75 DCHECK([[previewableContentsController_ activeContainer] window]); 76 tabStripController_.reset([[factory alloc] 77 initWithView:[self tabStripView] 78 switchView:[previewableContentsController_ activeContainer] 79 browser:browser_.get() 80 delegate:self]); 81} 82 83- (void)saveWindowPositionIfNeeded { 84 if (browser_ != BrowserList::GetLastActive()) 85 return; 86 87 if (!browser_->profile()->GetPrefs() || 88 !browser_->ShouldSaveWindowPlacement()) { 89 return; 90 } 91 92 [self saveWindowPositionToPrefs:browser_->profile()->GetPrefs()]; 93} 94 95- (void)saveWindowPositionToPrefs:(PrefService*)prefs { 96 // If we're in fullscreen mode, save the position of the regular window 97 // instead. 98 NSWindow* window = [self isFullscreen] ? savedRegularWindow_ : [self window]; 99 100 // Window positions are stored relative to the origin of the primary monitor. 101 NSRect monitorFrame = [[[NSScreen screens] objectAtIndex:0] frame]; 102 NSScreen* windowScreen = [window screen]; 103 104 // |windowScreen| can be nil (for example, if the monitor arrangement was 105 // changed while in fullscreen mode). If we see a nil screen, return without 106 // saving. 107 // TODO(rohitrao): We should just not save anything for fullscreen windows. 108 // http://crbug.com/36479. 109 if (!windowScreen) 110 return; 111 112 // Start with the window's frame, which is in virtual coordinates. 113 // Do some y twiddling to flip the coordinate system. 114 gfx::Rect bounds(NSRectToCGRect([window frame])); 115 bounds.set_y(monitorFrame.size.height - bounds.y() - bounds.height()); 116 117 // We also need to save the current work area, in flipped coordinates. 118 gfx::Rect workArea(NSRectToCGRect([windowScreen visibleFrame])); 119 workArea.set_y(monitorFrame.size.height - workArea.y() - workArea.height()); 120 121 // Browser::SaveWindowPlacement is used for session restore. 122 if (browser_->ShouldSaveWindowPlacement()) 123 browser_->SaveWindowPlacement(bounds, /*maximized=*/ false); 124 125 DictionaryPrefUpdate update(prefs, browser_->GetWindowPlacementKey().c_str()); 126 DictionaryValue* windowPreferences = update.Get(); 127 windowPreferences->SetInteger("left", bounds.x()); 128 windowPreferences->SetInteger("top", bounds.y()); 129 windowPreferences->SetInteger("right", bounds.right()); 130 windowPreferences->SetInteger("bottom", bounds.bottom()); 131 windowPreferences->SetBoolean("maximized", false); 132 windowPreferences->SetBoolean("always_on_top", false); 133 windowPreferences->SetInteger("work_area_left", workArea.x()); 134 windowPreferences->SetInteger("work_area_top", workArea.y()); 135 windowPreferences->SetInteger("work_area_right", workArea.right()); 136 windowPreferences->SetInteger("work_area_bottom", workArea.bottom()); 137} 138 139- (NSRect)window:(NSWindow*)window 140willPositionSheet:(NSWindow*)sheet 141 usingRect:(NSRect)defaultSheetRect { 142 // Position the sheet as follows: 143 // - If the bookmark bar is hidden or shown as a bubble (on the NTP when the 144 // bookmark bar is disabled), position the sheet immediately below the 145 // normal toolbar. 146 // - If the bookmark bar is shown (attached to the normal toolbar), position 147 // the sheet below the bookmark bar. 148 // - If the bookmark bar is currently animating, position the sheet according 149 // to where the bar will be when the animation ends. 150 switch ([bookmarkBarController_ visualState]) { 151 case bookmarks::kShowingState: { 152 NSRect bookmarkBarFrame = [[bookmarkBarController_ view] frame]; 153 defaultSheetRect.origin.y = bookmarkBarFrame.origin.y; 154 break; 155 } 156 case bookmarks::kHiddenState: 157 case bookmarks::kDetachedState: { 158 NSRect toolbarFrame = [[toolbarController_ view] frame]; 159 defaultSheetRect.origin.y = toolbarFrame.origin.y; 160 break; 161 } 162 case bookmarks::kInvalidState: 163 default: 164 NOTREACHED(); 165 } 166 return defaultSheetRect; 167} 168 169- (void)layoutSubviews { 170 // With the exception of the top tab strip, the subviews which we lay out are 171 // subviews of the content view, so we mainly work in the content view's 172 // coordinate system. Note, however, that the content view's coordinate system 173 // and the window's base coordinate system should coincide. 174 NSWindow* window = [self window]; 175 NSView* contentView = [window contentView]; 176 NSRect contentBounds = [contentView bounds]; 177 CGFloat minX = NSMinX(contentBounds); 178 CGFloat minY = NSMinY(contentBounds); 179 CGFloat width = NSWidth(contentBounds); 180 181 // Suppress title drawing if necessary. 182 if ([window respondsToSelector:@selector(setShouldHideTitle:)]) 183 [(id)window setShouldHideTitle:![self hasTitleBar]]; 184 185 BOOL isFullscreen = [self isFullscreen]; 186 CGFloat floatingBarHeight = [self floatingBarHeight]; 187 // In fullscreen mode, |yOffset| accounts for the sliding position of the 188 // floating bar and the extra offset needed to dodge the menu bar. 189 CGFloat yOffset = isFullscreen ? 190 (floor((1 - floatingBarShownFraction_) * floatingBarHeight) - 191 [fullscreenController_ floatingBarVerticalOffset]) : 0; 192 CGFloat maxY = NSMaxY(contentBounds) + yOffset; 193 CGFloat startMaxY = maxY; 194 195 if ([self hasTabStrip] && ![self useVerticalTabs]) { 196 // If we need to lay out the top tab strip, replace |maxY| and |startMaxY| 197 // with higher values, and then lay out the tab strip. 198 NSRect windowFrame = [contentView convertRect:[window frame] fromView:nil]; 199 startMaxY = maxY = NSHeight(windowFrame) + yOffset; 200 maxY = [self layoutTabStripAtMaxY:maxY width:width fullscreen:isFullscreen]; 201 } 202 203 // Sanity-check |maxY|. 204 DCHECK_GE(maxY, minY); 205 DCHECK_LE(maxY, NSMaxY(contentBounds) + yOffset); 206 207 // The base class already positions the side tab strip on the left side 208 // of the window's content area and sizes it to take the entire vertical 209 // height. All that's needed here is to push everything over to the right, 210 // if necessary. 211 if ([self useVerticalTabs]) { 212 const CGFloat sideTabWidth = [[self tabStripView] bounds].size.width; 213 minX += sideTabWidth; 214 width -= sideTabWidth; 215 } 216 217 // Place the toolbar at the top of the reserved area. 218 maxY = [self layoutToolbarAtMinX:minX maxY:maxY width:width]; 219 220 // If we're not displaying the bookmark bar below the infobar, then it goes 221 // immediately below the toolbar. 222 BOOL placeBookmarkBarBelowInfoBar = [self placeBookmarkBarBelowInfoBar]; 223 if (!placeBookmarkBarBelowInfoBar) 224 maxY = [self layoutBookmarkBarAtMinX:minX maxY:maxY width:width]; 225 226 // The floating bar backing view doesn't actually add any height. 227 NSRect floatingBarBackingRect = 228 NSMakeRect(minX, maxY, width, floatingBarHeight); 229 [self layoutFloatingBarBackingView:floatingBarBackingRect 230 fullscreen:isFullscreen]; 231 232 // Place the find bar immediately below the toolbar/attached bookmark bar. In 233 // fullscreen mode, it hangs off the top of the screen when the bar is hidden. 234 // The find bar is unaffected by the side tab positioning. 235 [findBarCocoaController_ positionFindBarViewAtMaxY:maxY maxWidth:width]; 236 237 // If in fullscreen mode, reset |maxY| to top of screen, so that the floating 238 // bar slides over the things which appear to be in the content area. 239 if (isFullscreen) 240 maxY = NSMaxY(contentBounds); 241 242 // Also place the infobar container immediate below the toolbar, except in 243 // fullscreen mode in which case it's at the top of the visual content area. 244 maxY = [self layoutInfoBarAtMinX:minX maxY:maxY width:width]; 245 246 // If the bookmark bar is detached, place it next in the visual content area. 247 if (placeBookmarkBarBelowInfoBar) 248 maxY = [self layoutBookmarkBarAtMinX:minX maxY:maxY width:width]; 249 250 // Place the download shelf, if any, at the bottom of the view. 251 minY = [self layoutDownloadShelfAtMinX:minX minY:minY width:width]; 252 253 // Finally, the content area takes up all of the remaining space. 254 NSRect contentAreaRect = NSMakeRect(minX, minY, width, maxY - minY); 255 [self layoutTabContentArea:contentAreaRect]; 256 257 // Normally, we don't need to tell the toolbar whether or not to show the 258 // divider, but things break down during animation. 259 [toolbarController_ 260 setDividerOpacity:[bookmarkBarController_ toolbarDividerOpacity]]; 261} 262 263- (CGFloat)floatingBarHeight { 264 if (![self isFullscreen]) 265 return 0; 266 267 CGFloat totalHeight = [fullscreenController_ floatingBarVerticalOffset]; 268 269 if ([self hasTabStrip]) 270 totalHeight += NSHeight([[self tabStripView] frame]); 271 272 if ([self hasToolbar]) { 273 totalHeight += NSHeight([[toolbarController_ view] frame]); 274 } else if ([self hasLocationBar]) { 275 totalHeight += NSHeight([[toolbarController_ view] frame]) + 276 kLocBarTopInset + kLocBarBottomInset; 277 } 278 279 if (![self placeBookmarkBarBelowInfoBar]) 280 totalHeight += NSHeight([[bookmarkBarController_ view] frame]); 281 282 return totalHeight; 283} 284 285- (CGFloat)layoutTabStripAtMaxY:(CGFloat)maxY 286 width:(CGFloat)width 287 fullscreen:(BOOL)fullscreen { 288 // Nothing to do if no tab strip. 289 if (![self hasTabStrip]) 290 return maxY; 291 292 NSView* tabStripView = [self tabStripView]; 293 CGFloat tabStripHeight = NSHeight([tabStripView frame]); 294 maxY -= tabStripHeight; 295 [tabStripView setFrame:NSMakeRect(0, maxY, width, tabStripHeight)]; 296 297 // Set indentation. 298 [tabStripController_ setIndentForControls:(fullscreen ? 0 : 299 [[tabStripController_ class] defaultIndentForControls])]; 300 301 // TODO(viettrungluu): Seems kind of bad -- shouldn't |-layoutSubviews| do 302 // this? Moreover, |-layoutTabs| will try to animate.... 303 [tabStripController_ layoutTabs]; 304 305 // Now lay out incognito badge together with the tab strip. 306 if (incognitoBadge_.get()) { 307 // Avoid the full-screen button. 308 CGFloat extraPadding = 0; 309 if ([[self window] respondsToSelector:@selector(toggleFullScreen:)]) { 310 NSButton* fullscreenButton = 311 [[self window] standardWindowButton:NSWindowFullScreenButton]; 312 if (fullscreenButton) 313 extraPadding += [fullscreenButton frame].size.width; 314 } 315 316 // Actually place the badge *above* |maxY|, by +2 to miss the divider. 317 NSPoint origin = NSMakePoint(width - NSWidth([incognitoBadge_ frame]) - 318 kIncognitoBadgeOffset - extraPadding, 319 maxY + 2); 320 [incognitoBadge_ setFrameOrigin:origin]; 321 [incognitoBadge_ setHidden:NO]; // Make sure it's shown. 322 } 323 324 return maxY; 325} 326 327- (CGFloat)layoutToolbarAtMinX:(CGFloat)minX 328 maxY:(CGFloat)maxY 329 width:(CGFloat)width { 330 NSView* toolbarView = [toolbarController_ view]; 331 NSRect toolbarFrame = [toolbarView frame]; 332 if ([self hasToolbar]) { 333 // The toolbar is present in the window, so we make room for it. 334 DCHECK(![toolbarView isHidden]); 335 toolbarFrame.origin.x = minX; 336 toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame); 337 toolbarFrame.size.width = width; 338 maxY -= NSHeight(toolbarFrame); 339 } else { 340 if ([self hasLocationBar]) { 341 // Location bar is present with no toolbar. Put a border of 342 // |kLocBar...Inset| pixels around the location bar. 343 // TODO(viettrungluu): This is moderately ridiculous. The toolbar should 344 // really be aware of what its height should be (the way the toolbar 345 // compression stuff is currently set up messes things up). 346 DCHECK(![toolbarView isHidden]); 347 toolbarFrame.origin.x = kLocBarLeftRightInset; 348 toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame) - kLocBarTopInset; 349 toolbarFrame.size.width = width - 2 * kLocBarLeftRightInset; 350 maxY -= kLocBarTopInset + NSHeight(toolbarFrame) + kLocBarBottomInset; 351 } else { 352 DCHECK([toolbarView isHidden]); 353 } 354 } 355 [toolbarView setFrame:toolbarFrame]; 356 return maxY; 357} 358 359- (BOOL)placeBookmarkBarBelowInfoBar { 360 // If we are currently displaying the NTP detached bookmark bar or animating 361 // to/from it (from/to anything else), we display the bookmark bar below the 362 // infobar. 363 return [bookmarkBarController_ isInState:bookmarks::kDetachedState] || 364 [bookmarkBarController_ isAnimatingToState:bookmarks::kDetachedState] || 365 [bookmarkBarController_ isAnimatingFromState:bookmarks::kDetachedState]; 366} 367 368- (CGFloat)layoutBookmarkBarAtMinX:(CGFloat)minX 369 maxY:(CGFloat)maxY 370 width:(CGFloat)width { 371 NSView* bookmarkBarView = [bookmarkBarController_ view]; 372 NSRect bookmarkBarFrame = [bookmarkBarView frame]; 373 BOOL oldHidden = [bookmarkBarView isHidden]; 374 BOOL newHidden = ![self isBookmarkBarVisible]; 375 if (oldHidden != newHidden) 376 [bookmarkBarView setHidden:newHidden]; 377 bookmarkBarFrame.origin.x = minX; 378 bookmarkBarFrame.origin.y = maxY - NSHeight(bookmarkBarFrame); 379 bookmarkBarFrame.size.width = width; 380 [bookmarkBarView setFrame:bookmarkBarFrame]; 381 maxY -= NSHeight(bookmarkBarFrame); 382 383 // TODO(viettrungluu): Does this really belong here? Calling it shouldn't be 384 // necessary in the non-NTP case. 385 [bookmarkBarController_ layoutSubviews]; 386 387 return maxY; 388} 389 390- (void)layoutFloatingBarBackingView:(NSRect)frame 391 fullscreen:(BOOL)fullscreen { 392 // Only display when in fullscreen mode. 393 if (fullscreen) { 394 // For certain window types such as app windows (e.g., the dev tools 395 // window), there's no actual overlay. (Displaying one would result in an 396 // overly sliding in only under the menu, which gives an ugly effect.) 397 if (floatingBarBackingView_.get()) { 398 BOOL aboveBookmarkBar = [self placeBookmarkBarBelowInfoBar]; 399 400 // Insert it into the view hierarchy if necessary. 401 if (![floatingBarBackingView_ superview] || 402 aboveBookmarkBar != floatingBarAboveBookmarkBar_) { 403 NSView* contentView = [[self window] contentView]; 404 // z-order gets messed up unless we explicitly remove the floatingbar 405 // view and re-add it. 406 [floatingBarBackingView_ removeFromSuperview]; 407 [contentView addSubview:floatingBarBackingView_ 408 positioned:(aboveBookmarkBar ? 409 NSWindowAbove : NSWindowBelow) 410 relativeTo:[bookmarkBarController_ view]]; 411 floatingBarAboveBookmarkBar_ = aboveBookmarkBar; 412 } 413 414 // Set its frame. 415 [floatingBarBackingView_ setFrame:frame]; 416 } 417 418 // But we want the logic to work as usual (for show/hide/etc. purposes). 419 [fullscreenController_ overlayFrameChanged:frame]; 420 } else { 421 // Okay to call even if |floatingBarBackingView_| is nil. 422 if ([floatingBarBackingView_ superview]) 423 [floatingBarBackingView_ removeFromSuperview]; 424 } 425} 426 427- (CGFloat)layoutInfoBarAtMinX:(CGFloat)minX 428 maxY:(CGFloat)maxY 429 width:(CGFloat)width { 430 NSView* containerView = [infoBarContainerController_ view]; 431 NSRect containerFrame = [containerView frame]; 432 maxY -= NSHeight(containerFrame); 433 maxY += [infoBarContainerController_ antiSpoofHeight]; 434 containerFrame.origin.x = minX; 435 containerFrame.origin.y = maxY; 436 containerFrame.size.width = width; 437 [containerView setFrame:containerFrame]; 438 return maxY; 439} 440 441- (CGFloat)layoutDownloadShelfAtMinX:(CGFloat)minX 442 minY:(CGFloat)minY 443 width:(CGFloat)width { 444 if (downloadShelfController_.get()) { 445 NSView* downloadView = [downloadShelfController_ view]; 446 NSRect downloadFrame = [downloadView frame]; 447 downloadFrame.origin.x = minX; 448 downloadFrame.origin.y = minY; 449 downloadFrame.size.width = width; 450 [downloadView setFrame:downloadFrame]; 451 minY += NSHeight(downloadFrame); 452 } 453 return minY; 454} 455 456- (void)layoutTabContentArea:(NSRect)newFrame { 457 NSView* tabContentView = [self tabContentArea]; 458 NSRect tabContentFrame = [tabContentView frame]; 459 460 bool contentShifted = 461 NSMaxY(tabContentFrame) != NSMaxY(newFrame) || 462 NSMinX(tabContentFrame) != NSMinX(newFrame); 463 464 tabContentFrame = newFrame; 465 [tabContentView setFrame:tabContentFrame]; 466 467 // If the relayout shifts the content area up or down, let the renderer know. 468 if (contentShifted) { 469 if (TabContents* contents = browser_->GetSelectedTabContents()) { 470 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 471 rwhv->WindowFrameChanged(); 472 } 473 } 474} 475 476- (BOOL)shouldShowBookmarkBar { 477 DCHECK(browser_.get()); 478 return browser_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar) ? 479 YES : NO; 480} 481 482- (BOOL)shouldShowDetachedBookmarkBar { 483 // NTP4 never detaches the bookmark bar. 484 if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kNewTabPage4)) 485 return NO; 486 487 DCHECK(browser_.get()); 488 TabContents* contents = browser_->GetSelectedTabContents(); 489 return (contents && 490 contents->ShouldShowBookmarkBar() && 491 ![previewableContentsController_ isShowingPreview]); 492} 493 494- (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression { 495 CGFloat newHeight = 496 [toolbarController_ desiredHeightForCompression:compression]; 497 NSRect toolbarFrame = [[toolbarController_ view] frame]; 498 CGFloat deltaH = newHeight - toolbarFrame.size.height; 499 500 if (deltaH == 0) 501 return; 502 503 toolbarFrame.size.height = newHeight; 504 NSRect bookmarkFrame = [[bookmarkBarController_ view] frame]; 505 bookmarkFrame.size.height = bookmarkFrame.size.height - deltaH; 506 [[toolbarController_ view] setFrame:toolbarFrame]; 507 [[bookmarkBarController_ view] setFrame:bookmarkFrame]; 508 [self layoutSubviews]; 509} 510 511// TODO(rohitrao): This function has shrunk into uselessness, and 512// |-setFullscreen:| has grown rather large. Find a good way to break up 513// |-setFullscreen:| into smaller pieces. http://crbug.com/36449 514- (void)adjustUIForFullscreen:(BOOL)fullscreen { 515 // Create the floating bar backing view if necessary. 516 if (fullscreen && !floatingBarBackingView_.get() && 517 ([self hasTabStrip] || [self hasToolbar] || [self hasLocationBar])) { 518 floatingBarBackingView_.reset( 519 [[FloatingBarBackingView alloc] initWithFrame:NSZeroRect]); 520 } 521} 522 523- (void)enableBarVisibilityUpdates { 524 // Early escape if there's nothing to do. 525 if (barVisibilityUpdatesEnabled_) 526 return; 527 528 barVisibilityUpdatesEnabled_ = YES; 529 530 if ([barVisibilityLocks_ count]) 531 [fullscreenController_ ensureOverlayShownWithAnimation:NO delay:NO]; 532 else 533 [fullscreenController_ ensureOverlayHiddenWithAnimation:NO delay:NO]; 534} 535 536- (void)disableBarVisibilityUpdates { 537 // Early escape if there's nothing to do. 538 if (!barVisibilityUpdatesEnabled_) 539 return; 540 541 barVisibilityUpdatesEnabled_ = NO; 542 [fullscreenController_ cancelAnimationAndTimers]; 543} 544 545- (void)setUpOSFullScreenButton { 546 NSWindow* window = [self window]; 547 if ([window respondsToSelector:@selector(toggleFullScreen:)]) { 548 NSWindowCollectionBehavior behavior = [window collectionBehavior]; 549 behavior |= NSWindowCollectionBehaviorFullScreenPrimary; 550 [window setCollectionBehavior:behavior]; 551 552 NSButton* fullscreenButton = 553 [window standardWindowButton:NSWindowFullScreenButton]; 554 [fullscreenButton setAction:@selector(enterFullscreen:)]; 555 [fullscreenButton setTarget:self]; 556 } 557} 558 559@end // @implementation BrowserWindowController(Private) 560