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