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