blob: 33f7fc013b8dcc45dbf5dc5707a1778c9f329456 [file] [log] [blame]
// Copyright (c) 2012 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/ui/cocoa/tabs/tab_window_controller.h"
#include "base/logging.h"
#import "chrome/browser/ui/cocoa/browser_window_layout.h"
#import "chrome/browser/ui/cocoa/fast_resize_view.h"
#import "chrome/browser/ui/cocoa/framed_browser_window.h"
#import "chrome/browser/ui/cocoa/tabs/tab_strip_background_view.h"
#import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
#import "chrome/browser/ui/cocoa/themed_window.h"
#import "chrome/browser/ui/cocoa/version_independent_window.h"
#import "ui/base/cocoa/focus_tracker.h"
#include "ui/base/theme_provider.h"
@interface TabWindowController ()
- (void)setUseOverlay:(BOOL)useOverlay;
// The tab strip background view should always be inserted as the back-most
// subview of the root view. It cannot be a subview of the contentView, as that
// would cause it to become layer backed, which would cause it to draw on top
// of non-layer backed content like the window controls.
- (void)insertTabStripBackgroundViewIntoWindow:(NSWindow*)window;
@end
@interface TabWindowOverlayWindow : NSWindow
@end
@implementation TabWindowOverlayWindow
- (ui::ThemeProvider*)themeProvider {
if ([self parentWindow])
return [[[self parentWindow] windowController] themeProvider];
return NULL;
}
- (ThemedWindowStyle)themedWindowStyle {
if ([self parentWindow])
return [[[self parentWindow] windowController] themedWindowStyle];
return NO;
}
- (NSPoint)themeImagePositionForAlignment:(ThemeImageAlignment)alignment {
if ([self parentWindow]) {
return [[[self parentWindow] windowController]
themeImagePositionForAlignment:alignment];
}
return NSZeroPoint;
}
@end
@implementation TabWindowController
- (id)initTabWindowControllerWithTabStrip:(BOOL)hasTabStrip {
const CGFloat kDefaultWidth = 750;
const CGFloat kDefaultHeight = 600;
NSRect contentRect = NSMakeRect(60, 229, kDefaultWidth, kDefaultHeight);
base::scoped_nsobject<FramedBrowserWindow> window(
[[FramedBrowserWindow alloc] initWithContentRect:contentRect
hasTabStrip:hasTabStrip]);
[window setReleasedWhenClosed:YES];
[window setAutorecalculatesKeyViewLoop:YES];
if ((self = [super initWithWindow:window])) {
[[self window] setDelegate:self];
chromeContentView_.reset([[NSView alloc]
initWithFrame:NSMakeRect(0, 0, kDefaultWidth, kDefaultHeight)]);
[chromeContentView_
setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
[chromeContentView_ setWantsLayer:YES];
[[[self window] contentView] addSubview:chromeContentView_];
tabContentArea_.reset(
[[FastResizeView alloc] initWithFrame:[chromeContentView_ bounds]]);
[tabContentArea_ setAutoresizingMask:NSViewWidthSizable |
NSViewHeightSizable];
[chromeContentView_ addSubview:tabContentArea_];
// tabStripBackgroundView_ draws the theme image behind the tab strip area.
// When making a tab dragging window (setUseOverlay:), this view stays in
// the parent window so that it can be translucent, while the tab strip view
// moves to the child window and stays opaque.
NSView* windowView = [window cr_windowView];
tabStripBackgroundView_.reset([[TabStripBackgroundView alloc]
initWithFrame:NSMakeRect(0,
NSMaxY([windowView bounds]) -
kBrowserFrameViewPaintHeight,
NSWidth([windowView bounds]),
kBrowserFrameViewPaintHeight)]);
[tabStripBackgroundView_
setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
[self insertTabStripBackgroundViewIntoWindow:window];
[self moveContentViewToBack:[window contentView]];
tabStripView_.reset([[TabStripView alloc]
initWithFrame:NSMakeRect(
0, 0, kDefaultWidth, chrome::kTabStripHeight)]);
[tabStripView_ setAutoresizingMask:NSViewWidthSizable |
NSViewMinYMargin];
if (hasTabStrip)
[self insertTabStripView:tabStripView_ intoWindow:[self window]];
}
return self;
}
- (NSView*)tabStripBackgroundView {
return tabStripBackgroundView_;
}
- (TabStripView*)tabStripView {
return tabStripView_;
}
- (FastResizeView*)tabContentArea {
return tabContentArea_;
}
- (NSView*)chromeContentView {
return chromeContentView_;
}
- (void)removeOverlay {
[self setUseOverlay:NO];
if (closeDeferred_) {
// See comment in BrowserWindowCocoa::Close() about orderOut:.
[[self window] orderOut:self];
[[self window] performClose:self]; // Autoreleases the controller.
}
}
- (void)showOverlay {
[self setUseOverlay:YES];
}
// If |useOverlay| is YES, creates a new overlay window and puts the tab strip
// and the content area inside of it. This allows it to have a different opacity
// from the title bar. If NO, returns everything to the previous state and
// destroys the overlay window until it's needed again. The tab strip and window
// contents are returned to the original window.
- (void)setUseOverlay:(BOOL)useOverlay {
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(removeOverlay)
object:nil];
NSWindow* window = [self window];
if (useOverlay && !overlayWindow_) {
DCHECK(!originalContentView_);
overlayWindow_ = [[TabWindowOverlayWindow alloc]
initWithContentRect:[window frame]
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:YES];
[overlayWindow_ setTitle:@"overlay"];
[overlayWindow_ setBackgroundColor:[NSColor clearColor]];
[overlayWindow_ setOpaque:NO];
[overlayWindow_ setDelegate:self];
[[overlayWindow_ contentView] setWantsLayer:YES];
originalContentView_ = self.chromeContentView;
[window addChildWindow:overlayWindow_ ordered:NSWindowAbove];
// Explicitly set the responder to be nil here (for restoring later).
// If the first responder were to be left non-nil here then
// [RenderWidgethostViewCocoa resignFirstResponder] would be called,
// followed by RenderWidgetHost::Blur(), which would result in an unexpected
// loss of focus.
focusBeforeOverlay_.reset([[FocusTracker alloc] initWithWindow:window]);
[window makeFirstResponder:nil];
// Move the original window's tab strip view and content view to the overlay
// window. The content view is added as a subview of the overlay window's
// content view (rather than using setContentView:) because the overlay
// window has a different content size (due to it being borderless).
[[overlayWindow_ cr_windowView] addSubview:[self tabStripView]];
[[overlayWindow_ contentView] addSubview:originalContentView_];
[overlayWindow_ orderFront:nil];
} else if (!useOverlay && overlayWindow_) {
DCHECK(originalContentView_);
// Return the original window's tab strip view and content view to their
// places. The TabStripView always needs to be in front of the window's
// content view and therefore it should always be added after the content
// view is set.
[[window contentView] addSubview:originalContentView_
positioned:NSWindowBelow
relativeTo:nil];
originalContentView_.frame = [[window contentView] bounds];
[self insertTabStripView:[self tabStripView] intoWindow:window];
[[window cr_windowView] updateTrackingAreas];
[focusBeforeOverlay_ restoreFocusInWindow:window];
focusBeforeOverlay_.reset();
[window display];
[window removeChildWindow:overlayWindow_];
[overlayWindow_ orderOut:nil];
[overlayWindow_ release];
overlayWindow_ = nil;
originalContentView_ = nil;
} else {
NOTREACHED();
}
}
- (NSWindow*)overlayWindow {
return overlayWindow_;
}
- (BOOL)shouldConstrainFrameRect {
// If we currently have an overlay window, do not attempt to change the
// window's size, as our overlay window doesn't know how to resize properly.
return overlayWindow_ == nil;
}
- (BOOL)canReceiveFrom:(TabWindowController*)source {
// subclass must implement
NOTIMPLEMENTED();
return NO;
}
- (void)moveTabViews:(NSArray*)views
fromController:(TabWindowController*)dragController {
NOTIMPLEMENTED();
}
- (NSArray*)tabViews {
NOTIMPLEMENTED();
return nil;
}
- (NSView*)activeTabView {
NOTIMPLEMENTED();
return nil;
}
- (void)layoutTabs {
// subclass must implement
NOTIMPLEMENTED();
}
- (TabWindowController*)detachTabsToNewWindow:(NSArray*)tabViews
draggedTab:(NSView*)draggedTab {
// subclass must implement
NOTIMPLEMENTED();
return NULL;
}
- (void)insertPlaceholderForTab:(TabView*)tab frame:(NSRect)frame {
[self showNewTabButton:NO];
}
- (void)removePlaceholder {
[self showNewTabButton:YES];
}
- (BOOL)isDragSessionActive {
NOTIMPLEMENTED();
return NO;
}
- (BOOL)tabDraggingAllowed {
return YES;
}
- (BOOL)tabTearingAllowed {
return YES;
}
- (BOOL)windowMovementAllowed {
return YES;
}
- (BOOL)isTabFullyVisible:(TabView*)tab {
// Subclasses should implement this, but it's not necessary.
return YES;
}
- (void)showNewTabButton:(BOOL)show {
// subclass must implement
NOTIMPLEMENTED();
}
- (void)detachTabView:(NSView*)view {
// subclass must implement
NOTIMPLEMENTED();
}
- (NSInteger)numberOfTabs {
// subclass must implement
NOTIMPLEMENTED();
return 0;
}
- (BOOL)hasLiveTabs {
// subclass must implement
NOTIMPLEMENTED();
return NO;
}
- (NSString*)activeTabTitle {
// subclass must implement
NOTIMPLEMENTED();
return @"";
}
- (BOOL)hasTabStrip {
// Subclasses should implement this.
NOTIMPLEMENTED();
return YES;
}
- (BOOL)isTabDraggable:(NSView*)tabView {
// Subclasses should implement this.
NOTIMPLEMENTED();
return YES;
}
// Tell the window that it needs to call performClose: as soon as the current
// drag is complete. This prevents a window (and its overlay) from going away
// during a drag.
- (void)deferPerformClose {
closeDeferred_ = YES;
}
- (void)moveContentViewToBack:(NSView*)cv {
base::scoped_nsobject<NSView> contentView([cv retain]);
NSView* superview = [contentView superview];
[contentView removeFromSuperview];
DCHECK(tabStripBackgroundView_);
if (superview == [tabStripBackgroundView_ superview]) {
[superview addSubview:contentView
positioned:NSWindowAbove
relativeTo:tabStripBackgroundView_];
} else {
[superview addSubview:contentView positioned:NSWindowBelow relativeTo:nil];
}
}
- (void)insertTabStripView:(NSView*)tabStripView intoWindow:(NSWindow*)window {
NSView* contentParent = [window cr_windowView];
if (contentParent == [[window contentView] superview]) {
// Add the tab strip directly above the content view, if they are siblings.
[contentParent addSubview:tabStripView
positioned:NSWindowAbove
relativeTo:[window contentView]];
} else {
[contentParent addSubview:tabStripView];
}
}
- (void)insertTabStripBackgroundViewIntoWindow:(NSWindow*)window {
DCHECK(tabStripBackgroundView_);
NSView* rootView = [[window contentView] superview];
[rootView addSubview:tabStripBackgroundView_
positioned:NSWindowBelow
relativeTo:nil];
}
// Called when the size of the window content area has changed. Override to
// position specific views. Base class implementation does nothing.
- (void)layoutSubviews {
NOTIMPLEMENTED();
}
@end