// 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/ui/cocoa/tabs/tab_window_controller.h" #include "base/logging.h" #import "chrome/browser/ui/cocoa/focus_tracker.h" #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h" #import "chrome/browser/ui/cocoa/themed_window.h" #include "ui/base/theme_provider.h" @interface TabWindowController(PRIVATE) - (void)setUseOverlay:(BOOL)useOverlay; @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)themePatternPhase { if ([self parentWindow]) return [[[self parentWindow] windowController] themePatternPhase]; return NSZeroPoint; } @end @implementation TabWindowController @synthesize tabContentArea = tabContentArea_; - (id)initWithWindow:(NSWindow*)window { if ((self = [super initWithWindow:window]) != nil) { lockedTabs_.reset([[NSMutableSet alloc] initWithCapacity:10]); } return self; } // Add the side tab strip to the left side of the window's content area, // making it fill the full height of the content area. - (void)addSideTabStripToWindow { NSView* contentView = [[self window] contentView]; NSRect contentFrame = [contentView frame]; NSRect sideStripFrame = NSMakeRect(0, 0, NSWidth([sideTabStripView_ frame]), NSHeight(contentFrame)); [sideTabStripView_ setFrame:sideStripFrame]; [contentView addSubview:sideTabStripView_]; } // Add the top tab strop to the window, above the content box and add it to the // view hierarchy as a sibling of the content view so it can overlap with the // window frame. - (void)addTopTabStripToWindow { NSRect contentFrame = [tabContentArea_ frame]; NSRect tabFrame = NSMakeRect(0, NSMaxY(contentFrame), NSWidth(contentFrame), NSHeight([topTabStripView_ frame])); [topTabStripView_ setFrame:tabFrame]; NSView* contentParent = [[[self window] contentView] superview]; [contentParent addSubview:topTabStripView_]; } - (void)windowDidLoad { // Cache the difference in height between the window content area and the // tab content area. NSRect tabFrame = [tabContentArea_ frame]; NSRect contentFrame = [[[self window] contentView] frame]; contentAreaHeightDelta_ = NSHeight(contentFrame) - NSHeight(tabFrame); if ([self hasTabStrip]) { if ([self useVerticalTabs]) { // No top tabstrip so remove the tabContentArea offset. tabFrame.size.height = contentFrame.size.height; [tabContentArea_ setFrame:tabFrame]; [self addSideTabStripToWindow]; } else { [self addTopTabStripToWindow]; } } else { // No top tabstrip so remove the tabContentArea offset. tabFrame.size.height = contentFrame.size.height; [tabContentArea_ setFrame:tabFrame]; } } // Toggles from one display mode of the tab strip to another. Will automatically // call -layoutSubviews to reposition other content. - (void)toggleTabStripDisplayMode { // Adjust the size of the tab contents to either use more or less space, // depending on the direction of the toggle. This needs to be done prior to // adding back in the top tab strip as its position is based off the MaxY // of the tab content area. BOOL useVertical = [self useVerticalTabs]; NSRect tabContentsFrame = [tabContentArea_ frame]; tabContentsFrame.size.height += useVertical ? contentAreaHeightDelta_ : -contentAreaHeightDelta_; [tabContentArea_ setFrame:tabContentsFrame]; if (useVertical) { // Remove the top tab strip and add the sidebar in. [topTabStripView_ removeFromSuperview]; [self addSideTabStripToWindow]; } else { // Remove the side tab strip and add the top tab strip as a sibling of the // window's content area. [sideTabStripView_ removeFromSuperview]; NSRect tabContentsFrame = [tabContentArea_ frame]; tabContentsFrame.size.height -= contentAreaHeightDelta_; [tabContentArea_ setFrame:tabContentsFrame]; [self addTopTabStripToWindow]; } [self layoutSubviews]; } // Return the appropriate tab strip based on whether or not side tabs are // enabled. - (TabStripView*)tabStripView { if ([self useVerticalTabs]) return sideTabStripView_; return topTabStripView_; } - (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 true, we're moving views into the overlay's content // area. If false, we're moving out of the overlay back into the window's // content. - (void)moveViewsBetweenWindowAndOverlay:(BOOL)useOverlay { if (useOverlay) { [[[overlayWindow_ contentView] superview] addSubview:[self tabStripView]]; // Add the original window's content view as a subview of the overlay // window's content view. We cannot simply use setContentView: here because // the overlay window has a different content size (due to it being // borderless). [[overlayWindow_ contentView] addSubview:cachedContentView_]; } else { [[self window] setContentView:cachedContentView_]; // 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. [[[[self window] contentView] superview] addSubview:[self tabStripView]]; [[[[self window] contentView] superview] updateTrackingAreas]; } } // 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(!cachedContentView_); 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]; cachedContentView_ = [window contentView]; [window addChildWindow:overlayWindow_ ordered:NSWindowAbove]; // Sets explictly nil to the responder and then restores it. // Leaving the first responder non-null here // causes [RenderWidgethostViewCocoa resignFirstResponder] and // following RenderWidgetHost::Blur(), which results unexpected // focus lost. focusBeforeOverlay_.reset([[FocusTracker alloc] initWithWindow:window]); [window makeFirstResponder:nil]; [self moveViewsBetweenWindowAndOverlay:useOverlay]; [overlayWindow_ orderFront:nil]; } else if (!useOverlay && overlayWindow_) { DCHECK(cachedContentView_); [window setContentView:cachedContentView_]; [self moveViewsBetweenWindowAndOverlay:useOverlay]; [focusBeforeOverlay_ restoreFocusInWindow:window]; focusBeforeOverlay_.reset(nil); [window display]; [window removeChildWindow:overlayWindow_]; [overlayWindow_ orderOut:nil]; [overlayWindow_ release]; overlayWindow_ = nil; cachedContentView_ = 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)moveTabView:(NSView*)view fromController:(TabWindowController*)dragController { NOTIMPLEMENTED(); } - (NSView*)selectedTabView { NOTIMPLEMENTED(); return nil; } - (void)layoutTabs { // subclass must implement NOTIMPLEMENTED(); } - (TabWindowController*)detachTabToNewWindow:(TabView*)tabView { // subclass must implement NOTIMPLEMENTED(); return NULL; } - (void)insertPlaceholderForTab:(TabView*)tab frame:(NSRect)frame yStretchiness:(CGFloat)yStretchiness { [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*)selectedTabTitle { // subclass must implement NOTIMPLEMENTED(); return @""; } - (BOOL)hasTabStrip { // Subclasses should implement this. NOTIMPLEMENTED(); return YES; } - (BOOL)useVerticalTabs { // Subclasses should implement this. NOTIMPLEMENTED(); return NO; } - (BOOL)isTabDraggable:(NSView*)tabView { return ![lockedTabs_ containsObject:tabView]; } - (void)setTab:(NSView*)tabView isDraggable:(BOOL)draggable { if (draggable) [lockedTabs_ removeObject:tabView]; else [lockedTabs_ addObject:tabView]; } // 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; } // 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