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