Home | History | Annotate | Download | only in tabs
      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