1// Copyright (c) 2010 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/tabs/tab_window_controller.h" 6 7#include "base/logging.h" 8#import "chrome/browser/ui/cocoa/focus_tracker.h" 9#import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h" 10#import "chrome/browser/ui/cocoa/themed_window.h" 11#include "ui/base/theme_provider.h" 12 13@interface TabWindowController(PRIVATE) 14- (void)setUseOverlay:(BOOL)useOverlay; 15@end 16 17@interface TabWindowOverlayWindow : NSWindow 18@end 19 20@implementation TabWindowOverlayWindow 21 22- (ui::ThemeProvider*)themeProvider { 23 if ([self parentWindow]) 24 return [[[self parentWindow] windowController] themeProvider]; 25 return NULL; 26} 27 28- (ThemedWindowStyle)themedWindowStyle { 29 if ([self parentWindow]) 30 return [[[self parentWindow] windowController] themedWindowStyle]; 31 return NO; 32} 33 34- (NSPoint)themePatternPhase { 35 if ([self parentWindow]) 36 return [[[self parentWindow] windowController] themePatternPhase]; 37 return NSZeroPoint; 38} 39 40@end 41 42@implementation TabWindowController 43@synthesize tabContentArea = tabContentArea_; 44 45- (id)initWithWindow:(NSWindow*)window { 46 if ((self = [super initWithWindow:window]) != nil) { 47 lockedTabs_.reset([[NSMutableSet alloc] initWithCapacity:10]); 48 } 49 return self; 50} 51 52// Add the side tab strip to the left side of the window's content area, 53// making it fill the full height of the content area. 54- (void)addSideTabStripToWindow { 55 NSView* contentView = [[self window] contentView]; 56 NSRect contentFrame = [contentView frame]; 57 NSRect sideStripFrame = 58 NSMakeRect(0, 0, 59 NSWidth([sideTabStripView_ frame]), 60 NSHeight(contentFrame)); 61 [sideTabStripView_ setFrame:sideStripFrame]; 62 [contentView addSubview:sideTabStripView_]; 63} 64 65// Add the top tab strop to the window, above the content box and add it to the 66// view hierarchy as a sibling of the content view so it can overlap with the 67// window frame. 68- (void)addTopTabStripToWindow { 69 NSRect contentFrame = [tabContentArea_ frame]; 70 NSRect tabFrame = 71 NSMakeRect(0, NSMaxY(contentFrame), 72 NSWidth(contentFrame), 73 NSHeight([topTabStripView_ frame])); 74 [topTabStripView_ setFrame:tabFrame]; 75 NSView* contentParent = [[[self window] contentView] superview]; 76 [contentParent addSubview:topTabStripView_]; 77} 78 79- (void)windowDidLoad { 80 // Cache the difference in height between the window content area and the 81 // tab content area. 82 NSRect tabFrame = [tabContentArea_ frame]; 83 NSRect contentFrame = [[[self window] contentView] frame]; 84 contentAreaHeightDelta_ = NSHeight(contentFrame) - NSHeight(tabFrame); 85 86 if ([self hasTabStrip]) { 87 if ([self useVerticalTabs]) { 88 // No top tabstrip so remove the tabContentArea offset. 89 tabFrame.size.height = contentFrame.size.height; 90 [tabContentArea_ setFrame:tabFrame]; 91 [self addSideTabStripToWindow]; 92 } else { 93 [self addTopTabStripToWindow]; 94 } 95 } else { 96 // No top tabstrip so remove the tabContentArea offset. 97 tabFrame.size.height = contentFrame.size.height; 98 [tabContentArea_ setFrame:tabFrame]; 99 } 100} 101 102// Toggles from one display mode of the tab strip to another. Will automatically 103// call -layoutSubviews to reposition other content. 104- (void)toggleTabStripDisplayMode { 105 // Adjust the size of the tab contents to either use more or less space, 106 // depending on the direction of the toggle. This needs to be done prior to 107 // adding back in the top tab strip as its position is based off the MaxY 108 // of the tab content area. 109 BOOL useVertical = [self useVerticalTabs]; 110 NSRect tabContentsFrame = [tabContentArea_ frame]; 111 tabContentsFrame.size.height += useVertical ? 112 contentAreaHeightDelta_ : -contentAreaHeightDelta_; 113 [tabContentArea_ setFrame:tabContentsFrame]; 114 115 if (useVertical) { 116 // Remove the top tab strip and add the sidebar in. 117 [topTabStripView_ removeFromSuperview]; 118 [self addSideTabStripToWindow]; 119 } else { 120 // Remove the side tab strip and add the top tab strip as a sibling of the 121 // window's content area. 122 [sideTabStripView_ removeFromSuperview]; 123 NSRect tabContentsFrame = [tabContentArea_ frame]; 124 tabContentsFrame.size.height -= contentAreaHeightDelta_; 125 [tabContentArea_ setFrame:tabContentsFrame]; 126 [self addTopTabStripToWindow]; 127 } 128 129 [self layoutSubviews]; 130} 131 132// Return the appropriate tab strip based on whether or not side tabs are 133// enabled. 134- (TabStripView*)tabStripView { 135 if ([self useVerticalTabs]) 136 return sideTabStripView_; 137 return topTabStripView_; 138} 139 140- (void)removeOverlay { 141 [self setUseOverlay:NO]; 142 if (closeDeferred_) { 143 // See comment in BrowserWindowCocoa::Close() about orderOut:. 144 [[self window] orderOut:self]; 145 [[self window] performClose:self]; // Autoreleases the controller. 146 } 147} 148 149- (void)showOverlay { 150 [self setUseOverlay:YES]; 151} 152 153// if |useOverlay| is true, we're moving views into the overlay's content 154// area. If false, we're moving out of the overlay back into the window's 155// content. 156- (void)moveViewsBetweenWindowAndOverlay:(BOOL)useOverlay { 157 if (useOverlay) { 158 [[[overlayWindow_ contentView] superview] addSubview:[self tabStripView]]; 159 // Add the original window's content view as a subview of the overlay 160 // window's content view. We cannot simply use setContentView: here because 161 // the overlay window has a different content size (due to it being 162 // borderless). 163 [[overlayWindow_ contentView] addSubview:cachedContentView_]; 164 } else { 165 [[self window] setContentView:cachedContentView_]; 166 // The TabStripView always needs to be in front of the window's content 167 // view and therefore it should always be added after the content view is 168 // set. 169 [[[[self window] contentView] superview] addSubview:[self tabStripView]]; 170 [[[[self window] contentView] superview] updateTrackingAreas]; 171 } 172} 173 174// If |useOverlay| is YES, creates a new overlay window and puts the tab strip 175// and the content area inside of it. This allows it to have a different opacity 176// from the title bar. If NO, returns everything to the previous state and 177// destroys the overlay window until it's needed again. The tab strip and window 178// contents are returned to the original window. 179- (void)setUseOverlay:(BOOL)useOverlay { 180 [NSObject cancelPreviousPerformRequestsWithTarget:self 181 selector:@selector(removeOverlay) 182 object:nil]; 183 NSWindow* window = [self window]; 184 if (useOverlay && !overlayWindow_) { 185 DCHECK(!cachedContentView_); 186 overlayWindow_ = [[TabWindowOverlayWindow alloc] 187 initWithContentRect:[window frame] 188 styleMask:NSBorderlessWindowMask 189 backing:NSBackingStoreBuffered 190 defer:YES]; 191 [overlayWindow_ setTitle:@"overlay"]; 192 [overlayWindow_ setBackgroundColor:[NSColor clearColor]]; 193 [overlayWindow_ setOpaque:NO]; 194 [overlayWindow_ setDelegate:self]; 195 cachedContentView_ = [window contentView]; 196 [window addChildWindow:overlayWindow_ ordered:NSWindowAbove]; 197 // Sets explictly nil to the responder and then restores it. 198 // Leaving the first responder non-null here 199 // causes [RenderWidgethostViewCocoa resignFirstResponder] and 200 // following RenderWidgetHost::Blur(), which results unexpected 201 // focus lost. 202 focusBeforeOverlay_.reset([[FocusTracker alloc] initWithWindow:window]); 203 [window makeFirstResponder:nil]; 204 [self moveViewsBetweenWindowAndOverlay:useOverlay]; 205 [overlayWindow_ orderFront:nil]; 206 } else if (!useOverlay && overlayWindow_) { 207 DCHECK(cachedContentView_); 208 [window setContentView:cachedContentView_]; 209 [self moveViewsBetweenWindowAndOverlay:useOverlay]; 210 [focusBeforeOverlay_ restoreFocusInWindow:window]; 211 focusBeforeOverlay_.reset(nil); 212 [window display]; 213 [window removeChildWindow:overlayWindow_]; 214 [overlayWindow_ orderOut:nil]; 215 [overlayWindow_ release]; 216 overlayWindow_ = nil; 217 cachedContentView_ = nil; 218 } else { 219 NOTREACHED(); 220 } 221} 222 223- (NSWindow*)overlayWindow { 224 return overlayWindow_; 225} 226 227- (BOOL)shouldConstrainFrameRect { 228 // If we currently have an overlay window, do not attempt to change the 229 // window's size, as our overlay window doesn't know how to resize properly. 230 return overlayWindow_ == nil; 231} 232 233- (BOOL)canReceiveFrom:(TabWindowController*)source { 234 // subclass must implement 235 NOTIMPLEMENTED(); 236 return NO; 237} 238 239- (void)moveTabView:(NSView*)view 240 fromController:(TabWindowController*)dragController { 241 NOTIMPLEMENTED(); 242} 243 244- (NSView*)selectedTabView { 245 NOTIMPLEMENTED(); 246 return nil; 247} 248 249- (void)layoutTabs { 250 // subclass must implement 251 NOTIMPLEMENTED(); 252} 253 254- (TabWindowController*)detachTabToNewWindow:(TabView*)tabView { 255 // subclass must implement 256 NOTIMPLEMENTED(); 257 return NULL; 258} 259 260- (void)insertPlaceholderForTab:(TabView*)tab 261 frame:(NSRect)frame 262 yStretchiness:(CGFloat)yStretchiness { 263 [self showNewTabButton:NO]; 264} 265 266- (void)removePlaceholder { 267 [self showNewTabButton:YES]; 268} 269 270- (BOOL)isDragSessionActive { 271 NOTIMPLEMENTED(); 272 return NO; 273} 274 275- (BOOL)tabDraggingAllowed { 276 return YES; 277} 278 279- (BOOL)tabTearingAllowed { 280 return YES; 281} 282 283- (BOOL)windowMovementAllowed { 284 return YES; 285} 286 287- (BOOL)isTabFullyVisible:(TabView*)tab { 288 // Subclasses should implement this, but it's not necessary. 289 return YES; 290} 291 292- (void)showNewTabButton:(BOOL)show { 293 // subclass must implement 294 NOTIMPLEMENTED(); 295} 296 297- (void)detachTabView:(NSView*)view { 298 // subclass must implement 299 NOTIMPLEMENTED(); 300} 301 302- (NSInteger)numberOfTabs { 303 // subclass must implement 304 NOTIMPLEMENTED(); 305 return 0; 306} 307 308- (BOOL)hasLiveTabs { 309 // subclass must implement 310 NOTIMPLEMENTED(); 311 return NO; 312} 313 314- (NSString*)selectedTabTitle { 315 // subclass must implement 316 NOTIMPLEMENTED(); 317 return @""; 318} 319 320- (BOOL)hasTabStrip { 321 // Subclasses should implement this. 322 NOTIMPLEMENTED(); 323 return YES; 324} 325 326- (BOOL)useVerticalTabs { 327 // Subclasses should implement this. 328 NOTIMPLEMENTED(); 329 return NO; 330} 331 332- (BOOL)isTabDraggable:(NSView*)tabView { 333 return ![lockedTabs_ containsObject:tabView]; 334} 335 336- (void)setTab:(NSView*)tabView isDraggable:(BOOL)draggable { 337 if (draggable) 338 [lockedTabs_ removeObject:tabView]; 339 else 340 [lockedTabs_ addObject:tabView]; 341} 342 343// Tell the window that it needs to call performClose: as soon as the current 344// drag is complete. This prevents a window (and its overlay) from going away 345// during a drag. 346- (void)deferPerformClose { 347 closeDeferred_ = YES; 348} 349 350// Called when the size of the window content area has changed. Override to 351// position specific views. Base class implementation does nothing. 352- (void)layoutSubviews { 353 NOTIMPLEMENTED(); 354} 355 356@end 357