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