browser_window_controller_private.mm revision ddb351dbec246cf1fab5ec20d2d5520909041de1
1// Copyright (c) 2011 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/browser_window_controller_private.h"
6
7#include "base/command_line.h"
8#import "base/memory/scoped_nsobject.h"
9#include "chrome/browser/browser_process.h"
10#include "chrome/browser/prefs/pref_service.h"
11#include "chrome/browser/prefs/scoped_user_pref_update.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/ui/browser_list.h"
14#import "chrome/browser/ui/cocoa/fast_resize_view.h"
15#import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
16#import "chrome/browser/ui/cocoa/floating_bar_backing_view.h"
17#import "chrome/browser/ui/cocoa/framed_browser_window.h"
18#import "chrome/browser/ui/cocoa/fullscreen_controller.h"
19#import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
20#import "chrome/browser/ui/cocoa/tab_contents/previewable_contents_controller.h"
21#import "chrome/browser/ui/cocoa/tabs/side_tab_strip_controller.h"
22#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
23#import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
24#import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
25#include "chrome/common/chrome_switches.h"
26#include "chrome/common/pref_names.h"
27#include "content/browser/renderer_host/render_widget_host_view.h"
28#include "content/browser/tab_contents/tab_contents.h"
29#include "content/browser/tab_contents/tab_contents_view.h"
30
31namespace {
32
33// Space between the incognito badge and the right edge of the window.
34const CGFloat kIncognitoBadgeOffset = 4;
35
36// Insets for the location bar, used when the full toolbar is hidden.
37// TODO(viettrungluu): We can argue about the "correct" insetting; I like the
38// following best, though arguably 0 inset is better/more correct.
39const CGFloat kLocBarLeftRightInset = 1;
40const CGFloat kLocBarTopInset = 0;
41const CGFloat kLocBarBottomInset = 1;
42
43}  // end namespace
44
45// 10.7 adds public APIs for full-screen support. Provide the declaration so it
46// can be called below when building with the 10.5 SDK.
47#if !defined(MAC_OS_X_VERSION_10_7) || \
48MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
49
50@interface NSWindow (LionSDKDeclarations)
51- (void)toggleFullScreen:(id)sender;
52@end
53
54enum {
55  NSWindowFullScreenButton = 7
56};
57
58#endif  // MAC_OS_X_VERSION_10_7
59
60@implementation BrowserWindowController(Private)
61
62// Create the appropriate tab strip controller based on whether or not side
63// tabs are enabled.
64- (void)createTabStripController {
65  Class factory = [TabStripController class];
66  if ([self useVerticalTabs])
67    factory = [SideTabStripController class];
68
69  DCHECK([previewableContentsController_ activeContainer]);
70  DCHECK([[previewableContentsController_ activeContainer] window]);
71  tabStripController_.reset([[factory alloc]
72      initWithView:[self tabStripView]
73        switchView:[previewableContentsController_ activeContainer]
74           browser:browser_.get()
75          delegate:self]);
76}
77
78- (void)saveWindowPositionIfNeeded {
79  if (browser_ != BrowserList::GetLastActive())
80    return;
81
82  if (!browser_->profile()->GetPrefs() ||
83      !browser_->ShouldSaveWindowPlacement()) {
84    return;
85  }
86
87  [self saveWindowPositionToPrefs:browser_->profile()->GetPrefs()];
88}
89
90- (void)saveWindowPositionToPrefs:(PrefService*)prefs {
91  // If we're in fullscreen mode, save the position of the regular window
92  // instead.
93  NSWindow* window = [self isFullscreen] ? savedRegularWindow_ : [self window];
94
95  // Window positions are stored relative to the origin of the primary monitor.
96  NSRect monitorFrame = [[[NSScreen screens] objectAtIndex:0] frame];
97  NSScreen* windowScreen = [window screen];
98
99  // |windowScreen| can be nil (for example, if the monitor arrangement was
100  // changed while in fullscreen mode).  If we see a nil screen, return without
101  // saving.
102  // TODO(rohitrao): We should just not save anything for fullscreen windows.
103  // http://crbug.com/36479.
104  if (!windowScreen)
105    return;
106
107  // Start with the window's frame, which is in virtual coordinates.
108  // Do some y twiddling to flip the coordinate system.
109  gfx::Rect bounds(NSRectToCGRect([window frame]));
110  bounds.set_y(monitorFrame.size.height - bounds.y() - bounds.height());
111
112  // We also need to save the current work area, in flipped coordinates.
113  gfx::Rect workArea(NSRectToCGRect([windowScreen visibleFrame]));
114  workArea.set_y(monitorFrame.size.height - workArea.y() - workArea.height());
115
116  // Browser::SaveWindowPlacement is used for session restore.
117  if (browser_->ShouldSaveWindowPlacement())
118    browser_->SaveWindowPlacement(bounds, /*maximized=*/ false);
119
120  DictionaryPrefUpdate update(prefs, browser_->GetWindowPlacementKey().c_str());
121  DictionaryValue* windowPreferences = update.Get();
122  windowPreferences->SetInteger("left", bounds.x());
123  windowPreferences->SetInteger("top", bounds.y());
124  windowPreferences->SetInteger("right", bounds.right());
125  windowPreferences->SetInteger("bottom", bounds.bottom());
126  windowPreferences->SetBoolean("maximized", false);
127  windowPreferences->SetBoolean("always_on_top", false);
128  windowPreferences->SetInteger("work_area_left", workArea.x());
129  windowPreferences->SetInteger("work_area_top", workArea.y());
130  windowPreferences->SetInteger("work_area_right", workArea.right());
131  windowPreferences->SetInteger("work_area_bottom", workArea.bottom());
132}
133
134- (NSRect)window:(NSWindow*)window
135willPositionSheet:(NSWindow*)sheet
136       usingRect:(NSRect)defaultSheetRect {
137  // Position the sheet as follows:
138  //  - If the bookmark bar is hidden or shown as a bubble (on the NTP when the
139  //    bookmark bar is disabled), position the sheet immediately below the
140  //    normal toolbar.
141  //  - If the bookmark bar is shown (attached to the normal toolbar), position
142  //    the sheet below the bookmark bar.
143  //  - If the bookmark bar is currently animating, position the sheet according
144  //    to where the bar will be when the animation ends.
145  switch ([bookmarkBarController_ visualState]) {
146    case bookmarks::kShowingState: {
147      NSRect bookmarkBarFrame = [[bookmarkBarController_ view] frame];
148      defaultSheetRect.origin.y = bookmarkBarFrame.origin.y;
149      break;
150    }
151    case bookmarks::kHiddenState:
152    case bookmarks::kDetachedState: {
153      NSRect toolbarFrame = [[toolbarController_ view] frame];
154      defaultSheetRect.origin.y = toolbarFrame.origin.y;
155      break;
156    }
157    case bookmarks::kInvalidState:
158    default:
159      NOTREACHED();
160  }
161  return defaultSheetRect;
162}
163
164- (void)layoutSubviews {
165  // With the exception of the top tab strip, the subviews which we lay out are
166  // subviews of the content view, so we mainly work in the content view's
167  // coordinate system. Note, however, that the content view's coordinate system
168  // and the window's base coordinate system should coincide.
169  NSWindow* window = [self window];
170  NSView* contentView = [window contentView];
171  NSRect contentBounds = [contentView bounds];
172  CGFloat minX = NSMinX(contentBounds);
173  CGFloat minY = NSMinY(contentBounds);
174  CGFloat width = NSWidth(contentBounds);
175
176  // Suppress title drawing if necessary.
177  if ([window respondsToSelector:@selector(setShouldHideTitle:)])
178    [(id)window setShouldHideTitle:![self hasTitleBar]];
179
180  BOOL isFullscreen = [self isFullscreen];
181  CGFloat floatingBarHeight = [self floatingBarHeight];
182  // In fullscreen mode, |yOffset| accounts for the sliding position of the
183  // floating bar and the extra offset needed to dodge the menu bar.
184  CGFloat yOffset = isFullscreen ?
185      (floor((1 - floatingBarShownFraction_) * floatingBarHeight) -
186          [fullscreenController_ floatingBarVerticalOffset]) : 0;
187  CGFloat maxY = NSMaxY(contentBounds) + yOffset;
188  CGFloat startMaxY = maxY;
189
190  if ([self hasTabStrip] && ![self useVerticalTabs]) {
191    // If we need to lay out the top tab strip, replace |maxY| and |startMaxY|
192    // with higher values, and then lay out the tab strip.
193    NSRect windowFrame = [contentView convertRect:[window frame] fromView:nil];
194    startMaxY = maxY = NSHeight(windowFrame) + yOffset;
195    maxY = [self layoutTabStripAtMaxY:maxY width:width fullscreen:isFullscreen];
196  }
197
198  // Sanity-check |maxY|.
199  DCHECK_GE(maxY, minY);
200  DCHECK_LE(maxY, NSMaxY(contentBounds) + yOffset);
201
202  // The base class already positions the side tab strip on the left side
203  // of the window's content area and sizes it to take the entire vertical
204  // height. All that's needed here is to push everything over to the right,
205  // if necessary.
206  if ([self useVerticalTabs]) {
207    const CGFloat sideTabWidth = [[self tabStripView] bounds].size.width;
208    minX += sideTabWidth;
209    width -= sideTabWidth;
210  }
211
212  // Place the toolbar at the top of the reserved area.
213  maxY = [self layoutToolbarAtMinX:minX maxY:maxY width:width];
214
215  // If we're not displaying the bookmark bar below the infobar, then it goes
216  // immediately below the toolbar.
217  BOOL placeBookmarkBarBelowInfoBar = [self placeBookmarkBarBelowInfoBar];
218  if (!placeBookmarkBarBelowInfoBar)
219    maxY = [self layoutBookmarkBarAtMinX:minX maxY:maxY width:width];
220
221  // The floating bar backing view doesn't actually add any height.
222  NSRect floatingBarBackingRect =
223      NSMakeRect(minX, maxY, width, floatingBarHeight);
224  [self layoutFloatingBarBackingView:floatingBarBackingRect
225                          fullscreen:isFullscreen];
226
227  // Place the find bar immediately below the toolbar/attached bookmark bar. In
228  // fullscreen mode, it hangs off the top of the screen when the bar is hidden.
229  // The find bar is unaffected by the side tab positioning.
230  [findBarCocoaController_ positionFindBarViewAtMaxY:maxY maxWidth:width];
231
232  // If in fullscreen mode, reset |maxY| to top of screen, so that the floating
233  // bar slides over the things which appear to be in the content area.
234  if (isFullscreen)
235    maxY = NSMaxY(contentBounds);
236
237  // Also place the infobar container immediate below the toolbar, except in
238  // fullscreen mode in which case it's at the top of the visual content area.
239  maxY = [self layoutInfoBarAtMinX:minX maxY:maxY width:width];
240
241  // If the bookmark bar is detached, place it next in the visual content area.
242  if (placeBookmarkBarBelowInfoBar)
243    maxY = [self layoutBookmarkBarAtMinX:minX maxY:maxY width:width];
244
245  // Place the download shelf, if any, at the bottom of the view.
246  minY = [self layoutDownloadShelfAtMinX:minX minY:minY width:width];
247
248  // Finally, the content area takes up all of the remaining space.
249  NSRect contentAreaRect = NSMakeRect(minX, minY, width, maxY - minY);
250  [self layoutTabContentArea:contentAreaRect];
251
252  // Normally, we don't need to tell the toolbar whether or not to show the
253  // divider, but things break down during animation.
254  [toolbarController_
255      setDividerOpacity:[bookmarkBarController_ toolbarDividerOpacity]];
256}
257
258- (CGFloat)floatingBarHeight {
259  if (![self isFullscreen])
260    return 0;
261
262  CGFloat totalHeight = [fullscreenController_ floatingBarVerticalOffset];
263
264  if ([self hasTabStrip])
265    totalHeight += NSHeight([[self tabStripView] frame]);
266
267  if ([self hasToolbar]) {
268    totalHeight += NSHeight([[toolbarController_ view] frame]);
269  } else if ([self hasLocationBar]) {
270    totalHeight += NSHeight([[toolbarController_ view] frame]) +
271                   kLocBarTopInset + kLocBarBottomInset;
272  }
273
274  if (![self placeBookmarkBarBelowInfoBar])
275    totalHeight += NSHeight([[bookmarkBarController_ view] frame]);
276
277  return totalHeight;
278}
279
280- (CGFloat)layoutTabStripAtMaxY:(CGFloat)maxY
281                          width:(CGFloat)width
282                     fullscreen:(BOOL)fullscreen {
283  // Nothing to do if no tab strip.
284  if (![self hasTabStrip])
285    return maxY;
286
287  NSView* tabStripView = [self tabStripView];
288  CGFloat tabStripHeight = NSHeight([tabStripView frame]);
289  maxY -= tabStripHeight;
290  [tabStripView setFrame:NSMakeRect(0, maxY, width, tabStripHeight)];
291
292  // Set indentation.
293  [tabStripController_ setIndentForControls:(fullscreen ? 0 :
294      [[tabStripController_ class] defaultIndentForControls])];
295
296  // TODO(viettrungluu): Seems kind of bad -- shouldn't |-layoutSubviews| do
297  // this? Moreover, |-layoutTabs| will try to animate....
298  [tabStripController_ layoutTabs];
299
300  // Now lay out incognito badge together with the tab strip.
301  if (incognitoBadge_.get()) {
302    // Avoid the full-screen button.
303    CGFloat extraPadding = 0;
304    if ([[self window] respondsToSelector:@selector(toggleFullScreen:)]) {
305      NSButton* fullscreenButton =
306          [[self window] standardWindowButton:NSWindowFullScreenButton];
307      if (fullscreenButton)
308        extraPadding += [fullscreenButton frame].size.width;
309    }
310
311    // Actually place the badge *above* |maxY|, by +2 to miss the divider.
312    NSPoint origin = NSMakePoint(width - NSWidth([incognitoBadge_ frame]) -
313                                     kIncognitoBadgeOffset - extraPadding,
314                                 maxY + 2);
315    [incognitoBadge_ setFrameOrigin:origin];
316    [incognitoBadge_ setHidden:NO];  // Make sure it's shown.
317  }
318
319  return maxY;
320}
321
322- (CGFloat)layoutToolbarAtMinX:(CGFloat)minX
323                          maxY:(CGFloat)maxY
324                         width:(CGFloat)width {
325  NSView* toolbarView = [toolbarController_ view];
326  NSRect toolbarFrame = [toolbarView frame];
327  if ([self hasToolbar]) {
328    // The toolbar is present in the window, so we make room for it.
329    DCHECK(![toolbarView isHidden]);
330    toolbarFrame.origin.x = minX;
331    toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame);
332    toolbarFrame.size.width = width;
333    maxY -= NSHeight(toolbarFrame);
334  } else {
335    if ([self hasLocationBar]) {
336      // Location bar is present with no toolbar. Put a border of
337      // |kLocBar...Inset| pixels around the location bar.
338      // TODO(viettrungluu): This is moderately ridiculous. The toolbar should
339      // really be aware of what its height should be (the way the toolbar
340      // compression stuff is currently set up messes things up).
341      DCHECK(![toolbarView isHidden]);
342      toolbarFrame.origin.x = kLocBarLeftRightInset;
343      toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame) - kLocBarTopInset;
344      toolbarFrame.size.width = width - 2 * kLocBarLeftRightInset;
345      maxY -= kLocBarTopInset + NSHeight(toolbarFrame) + kLocBarBottomInset;
346    } else {
347      DCHECK([toolbarView isHidden]);
348    }
349  }
350  [toolbarView setFrame:toolbarFrame];
351  return maxY;
352}
353
354- (BOOL)placeBookmarkBarBelowInfoBar {
355  // If we are currently displaying the NTP detached bookmark bar or animating
356  // to/from it (from/to anything else), we display the bookmark bar below the
357  // infobar.
358  return [bookmarkBarController_ isInState:bookmarks::kDetachedState] ||
359      [bookmarkBarController_ isAnimatingToState:bookmarks::kDetachedState] ||
360      [bookmarkBarController_ isAnimatingFromState:bookmarks::kDetachedState];
361}
362
363- (CGFloat)layoutBookmarkBarAtMinX:(CGFloat)minX
364                              maxY:(CGFloat)maxY
365                             width:(CGFloat)width {
366  NSView* bookmarkBarView = [bookmarkBarController_ view];
367  NSRect bookmarkBarFrame = [bookmarkBarView frame];
368  BOOL oldHidden = [bookmarkBarView isHidden];
369  BOOL newHidden = ![self isBookmarkBarVisible];
370  if (oldHidden != newHidden)
371    [bookmarkBarView setHidden:newHidden];
372  bookmarkBarFrame.origin.x = minX;
373  bookmarkBarFrame.origin.y = maxY - NSHeight(bookmarkBarFrame);
374  bookmarkBarFrame.size.width = width;
375  [bookmarkBarView setFrame:bookmarkBarFrame];
376  maxY -= NSHeight(bookmarkBarFrame);
377
378  // TODO(viettrungluu): Does this really belong here? Calling it shouldn't be
379  // necessary in the non-NTP case.
380  [bookmarkBarController_ layoutSubviews];
381
382  return maxY;
383}
384
385- (void)layoutFloatingBarBackingView:(NSRect)frame
386                          fullscreen:(BOOL)fullscreen {
387  // Only display when in fullscreen mode.
388  if (fullscreen) {
389    // For certain window types such as app windows (e.g., the dev tools
390    // window), there's no actual overlay. (Displaying one would result in an
391    // overly sliding in only under the menu, which gives an ugly effect.)
392    if (floatingBarBackingView_.get()) {
393      BOOL aboveBookmarkBar = [self placeBookmarkBarBelowInfoBar];
394
395      // Insert it into the view hierarchy if necessary.
396      if (![floatingBarBackingView_ superview] ||
397          aboveBookmarkBar != floatingBarAboveBookmarkBar_) {
398        NSView* contentView = [[self window] contentView];
399        // z-order gets messed up unless we explicitly remove the floatingbar
400        // view and re-add it.
401        [floatingBarBackingView_ removeFromSuperview];
402        [contentView addSubview:floatingBarBackingView_
403                     positioned:(aboveBookmarkBar ?
404                                     NSWindowAbove : NSWindowBelow)
405                     relativeTo:[bookmarkBarController_ view]];
406        floatingBarAboveBookmarkBar_ = aboveBookmarkBar;
407      }
408
409      // Set its frame.
410      [floatingBarBackingView_ setFrame:frame];
411    }
412
413    // But we want the logic to work as usual (for show/hide/etc. purposes).
414    [fullscreenController_ overlayFrameChanged:frame];
415  } else {
416    // Okay to call even if |floatingBarBackingView_| is nil.
417    if ([floatingBarBackingView_ superview])
418      [floatingBarBackingView_ removeFromSuperview];
419  }
420}
421
422- (CGFloat)layoutInfoBarAtMinX:(CGFloat)minX
423                          maxY:(CGFloat)maxY
424                         width:(CGFloat)width {
425  NSView* containerView = [infoBarContainerController_ view];
426  NSRect containerFrame = [containerView frame];
427  maxY -= NSHeight(containerFrame);
428  maxY += [infoBarContainerController_ antiSpoofHeight];
429  containerFrame.origin.x = minX;
430  containerFrame.origin.y = maxY;
431  containerFrame.size.width = width;
432  [containerView setFrame:containerFrame];
433  return maxY;
434}
435
436- (CGFloat)layoutDownloadShelfAtMinX:(CGFloat)minX
437                                minY:(CGFloat)minY
438                               width:(CGFloat)width {
439  if (downloadShelfController_.get()) {
440    NSView* downloadView = [downloadShelfController_ view];
441    NSRect downloadFrame = [downloadView frame];
442    downloadFrame.origin.x = minX;
443    downloadFrame.origin.y = minY;
444    downloadFrame.size.width = width;
445    [downloadView setFrame:downloadFrame];
446    minY += NSHeight(downloadFrame);
447  }
448  return minY;
449}
450
451- (void)layoutTabContentArea:(NSRect)newFrame {
452  NSView* tabContentView = [self tabContentArea];
453  NSRect tabContentFrame = [tabContentView frame];
454
455  bool contentShifted =
456      NSMaxY(tabContentFrame) != NSMaxY(newFrame) ||
457      NSMinX(tabContentFrame) != NSMinX(newFrame);
458
459  tabContentFrame = newFrame;
460  [tabContentView setFrame:tabContentFrame];
461
462  // If the relayout shifts the content area up or down, let the renderer know.
463  if (contentShifted) {
464    if (TabContents* contents = browser_->GetSelectedTabContents()) {
465      if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
466        rwhv->WindowFrameChanged();
467    }
468  }
469}
470
471- (BOOL)shouldShowBookmarkBar {
472  DCHECK(browser_.get());
473  return browser_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar) ?
474      YES : NO;
475}
476
477- (BOOL)shouldShowDetachedBookmarkBar {
478  // NTP4 never detaches the bookmark bar.
479  if (CommandLine::ForCurrentProcess()->HasSwitch(switches::kNewTabPage4))
480    return NO;
481
482  DCHECK(browser_.get());
483  TabContents* contents = browser_->GetSelectedTabContents();
484  return (contents &&
485          contents->ShouldShowBookmarkBar() &&
486          ![previewableContentsController_ isShowingPreview]);
487}
488
489- (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression {
490  CGFloat newHeight =
491      [toolbarController_ desiredHeightForCompression:compression];
492  NSRect toolbarFrame = [[toolbarController_ view] frame];
493  CGFloat deltaH = newHeight - toolbarFrame.size.height;
494
495  if (deltaH == 0)
496    return;
497
498  toolbarFrame.size.height = newHeight;
499  NSRect bookmarkFrame = [[bookmarkBarController_ view] frame];
500  bookmarkFrame.size.height = bookmarkFrame.size.height - deltaH;
501  [[toolbarController_ view] setFrame:toolbarFrame];
502  [[bookmarkBarController_ view] setFrame:bookmarkFrame];
503  [self layoutSubviews];
504}
505
506// TODO(rohitrao): This function has shrunk into uselessness, and
507// |-setFullscreen:| has grown rather large.  Find a good way to break up
508// |-setFullscreen:| into smaller pieces.  http://crbug.com/36449
509- (void)adjustUIForFullscreen:(BOOL)fullscreen {
510  // Create the floating bar backing view if necessary.
511  if (fullscreen && !floatingBarBackingView_.get() &&
512      ([self hasTabStrip] || [self hasToolbar] || [self hasLocationBar])) {
513    floatingBarBackingView_.reset(
514        [[FloatingBarBackingView alloc] initWithFrame:NSZeroRect]);
515  }
516}
517
518- (void)enableBarVisibilityUpdates {
519  // Early escape if there's nothing to do.
520  if (barVisibilityUpdatesEnabled_)
521    return;
522
523  barVisibilityUpdatesEnabled_ = YES;
524
525  if ([barVisibilityLocks_ count])
526    [fullscreenController_ ensureOverlayShownWithAnimation:NO delay:NO];
527  else
528    [fullscreenController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
529}
530
531- (void)disableBarVisibilityUpdates {
532  // Early escape if there's nothing to do.
533  if (!barVisibilityUpdatesEnabled_)
534    return;
535
536  barVisibilityUpdatesEnabled_ = NO;
537  [fullscreenController_ cancelAnimationAndTimers];
538}
539
540@end  // @implementation BrowserWindowController(Private)
541