• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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