1// Copyright (c) 2012 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/browser_window_layout.h"
9#import "chrome/browser/ui/cocoa/fast_resize_view.h"
10#import "chrome/browser/ui/cocoa/framed_browser_window.h"
11#import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
12#import "chrome/browser/ui/cocoa/themed_window.h"
13#import "chrome/browser/ui/cocoa/version_independent_window.h"
14#import "ui/base/cocoa/focus_tracker.h"
15#include "ui/base/theme_provider.h"
16
17@interface TabWindowController(PRIVATE)
18- (void)setUseOverlay:(BOOL)useOverlay;
19@end
20
21@interface TabWindowOverlayWindow : NSWindow
22@end
23
24@implementation TabWindowOverlayWindow
25
26- (ui::ThemeProvider*)themeProvider {
27  if ([self parentWindow])
28    return [[[self parentWindow] windowController] themeProvider];
29  return NULL;
30}
31
32- (ThemedWindowStyle)themedWindowStyle {
33  if ([self parentWindow])
34    return [[[self parentWindow] windowController] themedWindowStyle];
35  return NO;
36}
37
38- (NSPoint)themeImagePositionForAlignment:(ThemeImageAlignment)alignment {
39  if ([self parentWindow]) {
40    return [[[self parentWindow] windowController]
41        themeImagePositionForAlignment:alignment];
42  }
43  return NSZeroPoint;
44}
45
46@end
47
48@implementation TabWindowController
49
50- (id)initTabWindowControllerWithTabStrip:(BOOL)hasTabStrip {
51  NSRect contentRect = NSMakeRect(60, 229, 750, 600);
52  base::scoped_nsobject<FramedBrowserWindow> window(
53      [[FramedBrowserWindow alloc] initWithContentRect:contentRect
54                                           hasTabStrip:hasTabStrip]);
55  [self moveContentViewToBack:[window contentView]];
56  [window setReleasedWhenClosed:YES];
57  [window setAutorecalculatesKeyViewLoop:YES];
58
59  if ((self = [super initWithWindow:window])) {
60    [[self window] setDelegate:self];
61
62    tabContentArea_.reset([[FastResizeView alloc] initWithFrame:
63        NSMakeRect(0, 0, 750, 600)]);
64    [tabContentArea_ setAutoresizingMask:NSViewWidthSizable |
65                                         NSViewHeightSizable];
66    [[[self window] contentView] addSubview:tabContentArea_];
67
68    tabStripView_.reset([[TabStripView alloc]
69        initWithFrame:NSMakeRect(0, 0, 750, chrome::kTabStripHeight)]);
70    [tabStripView_ setAutoresizingMask:NSViewWidthSizable |
71                                       NSViewMinYMargin];
72    if (hasTabStrip)
73      [self insertTabStripView:tabStripView_ intoWindow:[self window]];
74  }
75  return self;
76}
77
78- (TabStripView*)tabStripView {
79  return tabStripView_;
80}
81
82- (FastResizeView*)tabContentArea {
83  return tabContentArea_;
84}
85
86- (void)removeOverlay {
87  [self setUseOverlay:NO];
88  if (closeDeferred_) {
89    // See comment in BrowserWindowCocoa::Close() about orderOut:.
90    [[self window] orderOut:self];
91    [[self window] performClose:self];  // Autoreleases the controller.
92  }
93}
94
95- (void)showOverlay {
96  [self setUseOverlay:YES];
97}
98
99// If |useOverlay| is YES, creates a new overlay window and puts the tab strip
100// and the content area inside of it. This allows it to have a different opacity
101// from the title bar. If NO, returns everything to the previous state and
102// destroys the overlay window until it's needed again. The tab strip and window
103// contents are returned to the original window.
104- (void)setUseOverlay:(BOOL)useOverlay {
105  [NSObject cancelPreviousPerformRequestsWithTarget:self
106                                           selector:@selector(removeOverlay)
107                                             object:nil];
108  NSWindow* window = [self window];
109  if (useOverlay && !overlayWindow_) {
110    DCHECK(!originalContentView_);
111
112    overlayWindow_ = [[TabWindowOverlayWindow alloc]
113                         initWithContentRect:[window frame]
114                                   styleMask:NSBorderlessWindowMask
115                                     backing:NSBackingStoreBuffered
116                                       defer:YES];
117    [overlayWindow_ setTitle:@"overlay"];
118    [overlayWindow_ setBackgroundColor:[NSColor clearColor]];
119    [overlayWindow_ setOpaque:NO];
120    [overlayWindow_ setDelegate:self];
121
122    originalContentView_ = [window contentView];
123    [window addChildWindow:overlayWindow_ ordered:NSWindowAbove];
124
125    // Explicitly set the responder to be nil here (for restoring later).
126    // If the first responder were to be left non-nil here then
127    // [RenderWidgethostViewCocoa resignFirstResponder] would be called,
128    // followed by RenderWidgetHost::Blur(), which would result in an unexpected
129    // loss of focus.
130    focusBeforeOverlay_.reset([[FocusTracker alloc] initWithWindow:window]);
131    [window makeFirstResponder:nil];
132
133    // Move the original window's tab strip view and content view to the overlay
134    // window. The content view is added as a subview of the overlay window's
135    // content view (rather than using setContentView:) because the overlay
136    // window has a different content size (due to it being borderless).
137    [[overlayWindow_ cr_windowView] addSubview:[self tabStripView]];
138    [[overlayWindow_ contentView] addSubview:originalContentView_];
139
140    [overlayWindow_ orderFront:nil];
141  } else if (!useOverlay && overlayWindow_) {
142    DCHECK(originalContentView_);
143
144    // Return the original window's tab strip view and content view to their
145    // places. The TabStripView always needs to be in front of the window's
146    // content view and therefore it should always be added after the content
147    // view is set.
148    [window setContentView:originalContentView_];
149    [self moveContentViewToBack:originalContentView_];
150    [self insertTabStripView:[self tabStripView] intoWindow:window];
151    [[window cr_windowView] updateTrackingAreas];
152
153    [focusBeforeOverlay_ restoreFocusInWindow:window];
154    focusBeforeOverlay_.reset();
155
156    [window display];
157    [window removeChildWindow:overlayWindow_];
158
159    [overlayWindow_ orderOut:nil];
160    [overlayWindow_ release];
161    overlayWindow_ = nil;
162    originalContentView_ = nil;
163  } else {
164    NOTREACHED();
165  }
166}
167
168- (NSWindow*)overlayWindow {
169  return overlayWindow_;
170}
171
172- (BOOL)shouldConstrainFrameRect {
173  // If we currently have an overlay window, do not attempt to change the
174  // window's size, as our overlay window doesn't know how to resize properly.
175  return overlayWindow_ == nil;
176}
177
178- (BOOL)canReceiveFrom:(TabWindowController*)source {
179  // subclass must implement
180  NOTIMPLEMENTED();
181  return NO;
182}
183
184- (void)moveTabViews:(NSArray*)views
185      fromController:(TabWindowController*)dragController {
186  NOTIMPLEMENTED();
187}
188
189- (NSArray*)tabViews {
190  NOTIMPLEMENTED();
191  return nil;
192}
193
194- (NSView*)activeTabView {
195  NOTIMPLEMENTED();
196  return nil;
197}
198
199- (void)layoutTabs {
200  // subclass must implement
201  NOTIMPLEMENTED();
202}
203
204- (TabWindowController*)detachTabsToNewWindow:(NSArray*)tabViews
205                                   draggedTab:(NSView*)draggedTab {
206  // subclass must implement
207  NOTIMPLEMENTED();
208  return NULL;
209}
210
211- (void)insertPlaceholderForTab:(TabView*)tab frame:(NSRect)frame {
212  [self showNewTabButton:NO];
213}
214
215- (void)removePlaceholder {
216  [self showNewTabButton:YES];
217}
218
219- (BOOL)isDragSessionActive {
220  NOTIMPLEMENTED();
221  return NO;
222}
223
224- (BOOL)tabDraggingAllowed {
225  return YES;
226}
227
228- (BOOL)tabTearingAllowed {
229  return YES;
230}
231
232- (BOOL)windowMovementAllowed {
233  return YES;
234}
235
236- (BOOL)isTabFullyVisible:(TabView*)tab {
237  // Subclasses should implement this, but it's not necessary.
238  return YES;
239}
240
241- (void)showNewTabButton:(BOOL)show {
242  // subclass must implement
243  NOTIMPLEMENTED();
244}
245
246- (void)detachTabView:(NSView*)view {
247  // subclass must implement
248  NOTIMPLEMENTED();
249}
250
251- (NSInteger)numberOfTabs {
252  // subclass must implement
253  NOTIMPLEMENTED();
254  return 0;
255}
256
257- (BOOL)hasLiveTabs {
258  // subclass must implement
259  NOTIMPLEMENTED();
260  return NO;
261}
262
263- (NSString*)activeTabTitle {
264  // subclass must implement
265  NOTIMPLEMENTED();
266  return @"";
267}
268
269- (BOOL)hasTabStrip {
270  // Subclasses should implement this.
271  NOTIMPLEMENTED();
272  return YES;
273}
274
275- (BOOL)isTabDraggable:(NSView*)tabView {
276  // Subclasses should implement this.
277  NOTIMPLEMENTED();
278  return YES;
279}
280
281// Tell the window that it needs to call performClose: as soon as the current
282// drag is complete. This prevents a window (and its overlay) from going away
283// during a drag.
284- (void)deferPerformClose {
285  closeDeferred_ = YES;
286}
287
288- (void)moveContentViewToBack:(NSView*)cv {
289  base::scoped_nsobject<NSView> contentView([cv retain]);
290  NSView* superview = [contentView superview];
291  [contentView removeFromSuperview];
292  [superview addSubview:contentView positioned:NSWindowBelow relativeTo:nil];
293}
294
295- (void)insertTabStripView:(NSView*)tabStripView intoWindow:(NSWindow*)window {
296  NSView* contentParent = [window cr_windowView];
297  if (contentParent == [[window contentView] superview]) {
298    // Add the tab strip directly above the content view, if they are siblings.
299    [contentParent addSubview:tabStripView
300                   positioned:NSWindowAbove
301                   relativeTo:[window contentView]];
302  } else {
303    [contentParent addSubview:tabStripView];
304  }
305}
306
307// Called when the size of the window content area has changed. Override to
308// position specific views. Base class implementation does nothing.
309- (void)layoutSubviews {
310  NOTIMPLEMENTED();
311}
312
313@end
314