blob: cb4270d5723a5721c8ea8b12c2c845ba6d7a4acc [file] [log] [blame]
// Copyright (c) 2010 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/cocoa/browser_window_controller_private.h"
#include "base/mac_util.h"
#import "base/scoped_nsobject.h"
#include "chrome/browser/browser_process.h"
#import "chrome/browser/cocoa/fast_resize_view.h"
#import "chrome/browser/cocoa/find_bar_cocoa_controller.h"
#import "chrome/browser/cocoa/floating_bar_backing_view.h"
#import "chrome/browser/cocoa/framed_browser_window.h"
#import "chrome/browser/cocoa/fullscreen_controller.h"
#import "chrome/browser/cocoa/previewable_contents_controller.h"
#import "chrome/browser/cocoa/side_tab_strip_controller.h"
#import "chrome/browser/cocoa/tab_strip_controller.h"
#import "chrome/browser/cocoa/tab_strip_view.h"
#import "chrome/browser/cocoa/toolbar_controller.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/renderer_host/render_widget_host_view.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/browser/tab_contents/tab_contents_view.h"
#include "chrome/browser/themes/browser_theme_provider.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/common/pref_names.h"
namespace {
// Space between the incognito badge and the right edge of the window.
const CGFloat kIncognitoBadgeOffset = 4;
// Insets for the location bar, used when the full toolbar is hidden.
// TODO(viettrungluu): We can argue about the "correct" insetting; I like the
// following best, though arguably 0 inset is better/more correct.
const CGFloat kLocBarLeftRightInset = 1;
const CGFloat kLocBarTopInset = 0;
const CGFloat kLocBarBottomInset = 1;
} // end namespace
@implementation BrowserWindowController(Private)
// Create the appropriate tab strip controller based on whether or not side
// tabs are enabled.
- (void)createTabStripController {
Class factory = [TabStripController class];
if ([self useVerticalTabs])
factory = [SideTabStripController class];
DCHECK([previewableContentsController_ activeContainer]);
DCHECK([[previewableContentsController_ activeContainer] window]);
tabStripController_.reset([[factory alloc]
initWithView:[self tabStripView]
switchView:[previewableContentsController_ activeContainer]
browser:browser_.get()
delegate:self]);
}
- (void)saveWindowPositionIfNeeded {
if (browser_ != BrowserList::GetLastActive())
return;
if (!g_browser_process || !g_browser_process->local_state() ||
!browser_->ShouldSaveWindowPlacement())
return;
[self saveWindowPositionToPrefs:g_browser_process->local_state()];
}
- (void)saveWindowPositionToPrefs:(PrefService*)prefs {
// If we're in fullscreen mode, save the position of the regular window
// instead.
NSWindow* window = [self isFullscreen] ? savedRegularWindow_ : [self window];
// Window positions are stored relative to the origin of the primary monitor.
NSRect monitorFrame = [[[NSScreen screens] objectAtIndex:0] frame];
NSScreen* windowScreen = [window screen];
// |windowScreen| can be nil (for example, if the monitor arrangement was
// changed while in fullscreen mode). If we see a nil screen, return without
// saving.
// TODO(rohitrao): We should just not save anything for fullscreen windows.
// http://crbug.com/36479.
if (!windowScreen)
return;
// Start with the window's frame, which is in virtual coordinates.
// Do some y twiddling to flip the coordinate system.
gfx::Rect bounds(NSRectToCGRect([window frame]));
bounds.set_y(monitorFrame.size.height - bounds.y() - bounds.height());
// We also need to save the current work area, in flipped coordinates.
gfx::Rect workArea(NSRectToCGRect([windowScreen visibleFrame]));
workArea.set_y(monitorFrame.size.height - workArea.y() - workArea.height());
// Browser::SaveWindowPlacement is used for session restore.
if (browser_->ShouldSaveWindowPlacement())
browser_->SaveWindowPlacement(bounds, /*maximized=*/ false);
DictionaryValue* windowPreferences = prefs->GetMutableDictionary(
browser_->GetWindowPlacementKey().c_str());
windowPreferences->SetInteger("left", bounds.x());
windowPreferences->SetInteger("top", bounds.y());
windowPreferences->SetInteger("right", bounds.right());
windowPreferences->SetInteger("bottom", bounds.bottom());
windowPreferences->SetBoolean("maximized", false);
windowPreferences->SetBoolean("always_on_top", false);
windowPreferences->SetInteger("work_area_left", workArea.x());
windowPreferences->SetInteger("work_area_top", workArea.y());
windowPreferences->SetInteger("work_area_right", workArea.right());
windowPreferences->SetInteger("work_area_bottom", workArea.bottom());
}
- (NSRect)window:(NSWindow*)window
willPositionSheet:(NSWindow*)sheet
usingRect:(NSRect)defaultSheetRect {
// Position the sheet as follows:
// - If the bookmark bar is hidden or shown as a bubble (on the NTP when the
// bookmark bar is disabled), position the sheet immediately below the
// normal toolbar.
// - If the bookmark bar is shown (attached to the normal toolbar), position
// the sheet below the bookmark bar.
// - If the bookmark bar is currently animating, position the sheet according
// to where the bar will be when the animation ends.
switch ([bookmarkBarController_ visualState]) {
case bookmarks::kShowingState: {
NSRect bookmarkBarFrame = [[bookmarkBarController_ view] frame];
defaultSheetRect.origin.y = bookmarkBarFrame.origin.y;
break;
}
case bookmarks::kHiddenState:
case bookmarks::kDetachedState: {
NSRect toolbarFrame = [[toolbarController_ view] frame];
defaultSheetRect.origin.y = toolbarFrame.origin.y;
break;
}
case bookmarks::kInvalidState:
default:
NOTREACHED();
}
return defaultSheetRect;
}
- (void)layoutSubviews {
// With the exception of the top tab strip, the subviews which we lay out are
// subviews of the content view, so we mainly work in the content view's
// coordinate system. Note, however, that the content view's coordinate system
// and the window's base coordinate system should coincide.
NSWindow* window = [self window];
NSView* contentView = [window contentView];
NSRect contentBounds = [contentView bounds];
CGFloat minX = NSMinX(contentBounds);
CGFloat minY = NSMinY(contentBounds);
CGFloat width = NSWidth(contentBounds);
// Suppress title drawing if necessary.
if ([window respondsToSelector:@selector(setShouldHideTitle:)])
[(id)window setShouldHideTitle:![self hasTitleBar]];
BOOL isFullscreen = [self isFullscreen];
CGFloat floatingBarHeight = [self floatingBarHeight];
// In fullscreen mode, |yOffset| accounts for the sliding position of the
// floating bar and the extra offset needed to dodge the menu bar.
CGFloat yOffset = isFullscreen ?
(floor((1 - floatingBarShownFraction_) * floatingBarHeight) -
[fullscreenController_ floatingBarVerticalOffset]) : 0;
CGFloat maxY = NSMaxY(contentBounds) + yOffset;
CGFloat startMaxY = maxY;
if ([self hasTabStrip] && ![self useVerticalTabs]) {
// If we need to lay out the top tab strip, replace |maxY| and |startMaxY|
// with higher values, and then lay out the tab strip.
NSRect windowFrame = [contentView convertRect:[window frame] fromView:nil];
startMaxY = maxY = NSHeight(windowFrame) + yOffset;
maxY = [self layoutTabStripAtMaxY:maxY width:width fullscreen:isFullscreen];
}
// Sanity-check |maxY|.
DCHECK_GE(maxY, minY);
DCHECK_LE(maxY, NSMaxY(contentBounds) + yOffset);
// The base class already positions the side tab strip on the left side
// of the window's content area and sizes it to take the entire vertical
// height. All that's needed here is to push everything over to the right,
// if necessary.
if ([self useVerticalTabs]) {
const CGFloat sideTabWidth = [[self tabStripView] bounds].size.width;
minX += sideTabWidth;
width -= sideTabWidth;
}
// Place the toolbar at the top of the reserved area.
maxY = [self layoutToolbarAtMinX:minX maxY:maxY width:width];
// If we're not displaying the bookmark bar below the infobar, then it goes
// immediately below the toolbar.
BOOL placeBookmarkBarBelowInfoBar = [self placeBookmarkBarBelowInfoBar];
if (!placeBookmarkBarBelowInfoBar)
maxY = [self layoutBookmarkBarAtMinX:minX maxY:maxY width:width];
// The floating bar backing view doesn't actually add any height.
NSRect floatingBarBackingRect =
NSMakeRect(minX, maxY, width, floatingBarHeight);
[self layoutFloatingBarBackingView:floatingBarBackingRect
fullscreen:isFullscreen];
// Place the find bar immediately below the toolbar/attached bookmark bar. In
// fullscreen mode, it hangs off the top of the screen when the bar is hidden.
// The find bar is unaffected by the side tab positioning.
[findBarCocoaController_ positionFindBarViewAtMaxY:maxY maxWidth:width];
// If in fullscreen mode, reset |maxY| to top of screen, so that the floating
// bar slides over the things which appear to be in the content area.
if (isFullscreen)
maxY = NSMaxY(contentBounds);
// Also place the infobar container immediate below the toolbar, except in
// fullscreen mode in which case it's at the top of the visual content area.
maxY = [self layoutInfoBarAtMinX:minX maxY:maxY width:width];
// If the bookmark bar is detached, place it next in the visual content area.
if (placeBookmarkBarBelowInfoBar)
maxY = [self layoutBookmarkBarAtMinX:minX maxY:maxY width:width];
// Place the download shelf, if any, at the bottom of the view.
minY = [self layoutDownloadShelfAtMinX:minX minY:minY width:width];
// Finally, the content area takes up all of the remaining space.
NSRect contentAreaRect = NSMakeRect(minX, minY, width, maxY - minY);
[self layoutTabContentArea:contentAreaRect];
// Normally, we don't need to tell the toolbar whether or not to show the
// divider, but things break down during animation.
[toolbarController_
setDividerOpacity:[bookmarkBarController_ toolbarDividerOpacity]];
}
- (CGFloat)floatingBarHeight {
if (![self isFullscreen])
return 0;
CGFloat totalHeight = [fullscreenController_ floatingBarVerticalOffset];
if ([self hasTabStrip])
totalHeight += NSHeight([[self tabStripView] frame]);
if ([self hasToolbar]) {
totalHeight += NSHeight([[toolbarController_ view] frame]);
} else if ([self hasLocationBar]) {
totalHeight += NSHeight([[toolbarController_ view] frame]) +
kLocBarTopInset + kLocBarBottomInset;
}
if (![self placeBookmarkBarBelowInfoBar])
totalHeight += NSHeight([[bookmarkBarController_ view] frame]);
return totalHeight;
}
- (CGFloat)layoutTabStripAtMaxY:(CGFloat)maxY
width:(CGFloat)width
fullscreen:(BOOL)fullscreen {
// Nothing to do if no tab strip.
if (![self hasTabStrip])
return maxY;
NSView* tabStripView = [self tabStripView];
CGFloat tabStripHeight = NSHeight([tabStripView frame]);
maxY -= tabStripHeight;
[tabStripView setFrame:NSMakeRect(0, maxY, width, tabStripHeight)];
// Set indentation.
[tabStripController_ setIndentForControls:(fullscreen ? 0 :
[[tabStripController_ class] defaultIndentForControls])];
// TODO(viettrungluu): Seems kind of bad -- shouldn't |-layoutSubviews| do
// this? Moreover, |-layoutTabs| will try to animate....
[tabStripController_ layoutTabs];
// Now lay out incognito badge together with the tab strip.
if (incognitoBadge_.get()) {
// Actually place the badge *above* |maxY|.
NSPoint origin = NSMakePoint(width - NSWidth([incognitoBadge_ frame]) -
kIncognitoBadgeOffset, maxY);
[incognitoBadge_ setFrameOrigin:origin];
[incognitoBadge_ setHidden:NO]; // Make sure it's shown.
}
return maxY;
}
- (CGFloat)layoutToolbarAtMinX:(CGFloat)minX
maxY:(CGFloat)maxY
width:(CGFloat)width {
NSView* toolbarView = [toolbarController_ view];
NSRect toolbarFrame = [toolbarView frame];
if ([self hasToolbar]) {
// The toolbar is present in the window, so we make room for it.
DCHECK(![toolbarView isHidden]);
toolbarFrame.origin.x = minX;
toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame);
toolbarFrame.size.width = width;
maxY -= NSHeight(toolbarFrame);
} else {
if ([self hasLocationBar]) {
// Location bar is present with no toolbar. Put a border of
// |kLocBar...Inset| pixels around the location bar.
// TODO(viettrungluu): This is moderately ridiculous. The toolbar should
// really be aware of what its height should be (the way the toolbar
// compression stuff is currently set up messes things up).
DCHECK(![toolbarView isHidden]);
toolbarFrame.origin.x = kLocBarLeftRightInset;
toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame) - kLocBarTopInset;
toolbarFrame.size.width = width - 2 * kLocBarLeftRightInset;
maxY -= kLocBarTopInset + NSHeight(toolbarFrame) + kLocBarBottomInset;
} else {
DCHECK([toolbarView isHidden]);
}
}
[toolbarView setFrame:toolbarFrame];
return maxY;
}
- (BOOL)placeBookmarkBarBelowInfoBar {
// If we are currently displaying the NTP detached bookmark bar or animating
// to/from it (from/to anything else), we display the bookmark bar below the
// infobar.
return [bookmarkBarController_ isInState:bookmarks::kDetachedState] ||
[bookmarkBarController_ isAnimatingToState:bookmarks::kDetachedState] ||
[bookmarkBarController_ isAnimatingFromState:bookmarks::kDetachedState];
}
- (CGFloat)layoutBookmarkBarAtMinX:(CGFloat)minX
maxY:(CGFloat)maxY
width:(CGFloat)width {
NSView* bookmarkBarView = [bookmarkBarController_ view];
NSRect bookmarkBarFrame = [bookmarkBarView frame];
BOOL oldHidden = [bookmarkBarView isHidden];
BOOL newHidden = ![self isBookmarkBarVisible];
if (oldHidden != newHidden)
[bookmarkBarView setHidden:newHidden];
bookmarkBarFrame.origin.x = minX;
bookmarkBarFrame.origin.y = maxY - NSHeight(bookmarkBarFrame);
bookmarkBarFrame.size.width = width;
[bookmarkBarView setFrame:bookmarkBarFrame];
maxY -= NSHeight(bookmarkBarFrame);
// TODO(viettrungluu): Does this really belong here? Calling it shouldn't be
// necessary in the non-NTP case.
[bookmarkBarController_ layoutSubviews];
return maxY;
}
- (void)layoutFloatingBarBackingView:(NSRect)frame
fullscreen:(BOOL)fullscreen {
// Only display when in fullscreen mode.
if (fullscreen) {
// For certain window types such as app windows (e.g., the dev tools
// window), there's no actual overlay. (Displaying one would result in an
// overly sliding in only under the menu, which gives an ugly effect.)
if (floatingBarBackingView_.get()) {
BOOL aboveBookmarkBar = [self placeBookmarkBarBelowInfoBar];
// Insert it into the view hierarchy if necessary.
if (![floatingBarBackingView_ superview] ||
aboveBookmarkBar != floatingBarAboveBookmarkBar_) {
NSView* contentView = [[self window] contentView];
// z-order gets messed up unless we explicitly remove the floatingbar
// view and re-add it.
[floatingBarBackingView_ removeFromSuperview];
[contentView addSubview:floatingBarBackingView_
positioned:(aboveBookmarkBar ?
NSWindowAbove : NSWindowBelow)
relativeTo:[bookmarkBarController_ view]];
floatingBarAboveBookmarkBar_ = aboveBookmarkBar;
}
// Set its frame.
[floatingBarBackingView_ setFrame:frame];
}
// But we want the logic to work as usual (for show/hide/etc. purposes).
[fullscreenController_ overlayFrameChanged:frame];
} else {
// Okay to call even if |floatingBarBackingView_| is nil.
if ([floatingBarBackingView_ superview])
[floatingBarBackingView_ removeFromSuperview];
}
}
- (CGFloat)layoutInfoBarAtMinX:(CGFloat)minX
maxY:(CGFloat)maxY
width:(CGFloat)width {
NSView* infoBarView = [infoBarContainerController_ view];
NSRect infoBarFrame = [infoBarView frame];
infoBarFrame.origin.x = minX;
infoBarFrame.origin.y = maxY - NSHeight(infoBarFrame);
infoBarFrame.size.width = width;
[infoBarView setFrame:infoBarFrame];
maxY -= NSHeight(infoBarFrame);
return maxY;
}
- (CGFloat)layoutDownloadShelfAtMinX:(CGFloat)minX
minY:(CGFloat)minY
width:(CGFloat)width {
if (downloadShelfController_.get()) {
NSView* downloadView = [downloadShelfController_ view];
NSRect downloadFrame = [downloadView frame];
downloadFrame.origin.x = minX;
downloadFrame.origin.y = minY;
downloadFrame.size.width = width;
[downloadView setFrame:downloadFrame];
minY += NSHeight(downloadFrame);
}
return minY;
}
- (void)layoutTabContentArea:(NSRect)newFrame {
NSView* tabContentView = [self tabContentArea];
NSRect tabContentFrame = [tabContentView frame];
bool contentShifted =
NSMaxY(tabContentFrame) != NSMaxY(newFrame) ||
NSMinX(tabContentFrame) != NSMinX(newFrame);
tabContentFrame = newFrame;
[tabContentView setFrame:tabContentFrame];
// If the relayout shifts the content area up or down, let the renderer know.
if (contentShifted) {
if (TabContents* contents = browser_->GetSelectedTabContents()) {
if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
rwhv->WindowFrameChanged();
}
}
}
- (BOOL)shouldShowBookmarkBar {
DCHECK(browser_.get());
return browser_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar) ?
YES : NO;
}
- (BOOL)shouldShowDetachedBookmarkBar {
DCHECK(browser_.get());
TabContents* contents = browser_->GetSelectedTabContents();
return (contents &&
contents->ShouldShowBookmarkBar() &&
![previewableContentsController_ isShowingPreview]);
}
- (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression {
CGFloat newHeight =
[toolbarController_ desiredHeightForCompression:compression];
NSRect toolbarFrame = [[toolbarController_ view] frame];
CGFloat deltaH = newHeight - toolbarFrame.size.height;
if (deltaH == 0)
return;
toolbarFrame.size.height = newHeight;
NSRect bookmarkFrame = [[bookmarkBarController_ view] frame];
bookmarkFrame.size.height = bookmarkFrame.size.height - deltaH;
[[toolbarController_ view] setFrame:toolbarFrame];
[[bookmarkBarController_ view] setFrame:bookmarkFrame];
[self layoutSubviews];
}
// TODO(rohitrao): This function has shrunk into uselessness, and
// |-setFullscreen:| has grown rather large. Find a good way to break up
// |-setFullscreen:| into smaller pieces. http://crbug.com/36449
- (void)adjustUIForFullscreen:(BOOL)fullscreen {
// Create the floating bar backing view if necessary.
if (fullscreen && !floatingBarBackingView_.get() &&
([self hasTabStrip] || [self hasToolbar] || [self hasLocationBar])) {
floatingBarBackingView_.reset(
[[FloatingBarBackingView alloc] initWithFrame:NSZeroRect]);
}
}
- (void)enableBarVisibilityUpdates {
// Early escape if there's nothing to do.
if (barVisibilityUpdatesEnabled_)
return;
barVisibilityUpdatesEnabled_ = YES;
if ([barVisibilityLocks_ count])
[fullscreenController_ ensureOverlayShownWithAnimation:NO delay:NO];
else
[fullscreenController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
}
- (void)disableBarVisibilityUpdates {
// Early escape if there's nothing to do.
if (!barVisibilityUpdatesEnabled_)
return;
barVisibilityUpdatesEnabled_ = NO;
[fullscreenController_ cancelAnimationAndTimers];
}
@end // @implementation BrowserWindowController(Private)