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/framed_browser_window.h"
6
7#include "base/logging.h"
8#include "chrome/browser/global_keyboard_shortcuts_mac.h"
9#import "chrome/browser/ui/cocoa/browser_frame_view.h"
10#import "chrome/browser/ui/cocoa/browser_window_controller.h"
11#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
12#import "chrome/browser/ui/cocoa/themed_window.h"
13#import "chrome/browser/renderer_host/render_widget_host_view_mac.h"
14#include "chrome/browser/themes/theme_service.h"
15
16// Implementer's note: Moving the window controls is tricky. When altering the
17// code, ensure that:
18// - accessibility hit testing works
19// - the accessibility hierarchy is correct
20// - close/min in the background don't bring the window forward
21// - rollover effects work correctly
22
23namespace {
24
25// Size of the gradient. Empirically determined so that the gradient looks
26// like what the heuristic does when there are just a few tabs.
27const CGFloat kWindowGradientHeight = 24.0;
28
29}
30
31@interface FramedBrowserWindow (Private)
32
33- (void)adjustCloseButton:(NSNotification*)notification;
34- (void)adjustMiniaturizeButton:(NSNotification*)notification;
35- (void)adjustZoomButton:(NSNotification*)notification;
36- (void)adjustButton:(NSButton*)button
37              ofKind:(NSWindowButton)kind;
38- (NSView*)frameView;
39
40@end
41
42@implementation FramedBrowserWindow
43
44- (id)initWithContentRect:(NSRect)contentRect
45                styleMask:(NSUInteger)aStyle
46                  backing:(NSBackingStoreType)bufferingType
47                    defer:(BOOL)flag {
48  if ((self = [super initWithContentRect:contentRect
49                               styleMask:aStyle
50                                 backing:bufferingType
51                                   defer:flag])) {
52    if (aStyle & NSTexturedBackgroundWindowMask) {
53      // The following two calls fix http://www.crbug.com/25684 by preventing
54      // the window from recalculating the border thickness as the window is
55      // resized.
56      // This was causing the window tint to change for the default system theme
57      // when the window was being resized.
58      [self setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
59      [self setContentBorderThickness:kWindowGradientHeight forEdge:NSMaxYEdge];
60    }
61
62    closeButton_ = [self standardWindowButton:NSWindowCloseButton];
63    [closeButton_ setPostsFrameChangedNotifications:YES];
64    miniaturizeButton_ = [self standardWindowButton:NSWindowMiniaturizeButton];
65    [miniaturizeButton_ setPostsFrameChangedNotifications:YES];
66    zoomButton_ = [self standardWindowButton:NSWindowZoomButton];
67    [zoomButton_ setPostsFrameChangedNotifications:YES];
68
69    NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
70    [center addObserver:self
71               selector:@selector(adjustCloseButton:)
72                   name:NSViewFrameDidChangeNotification
73                 object:closeButton_];
74    [center addObserver:self
75               selector:@selector(adjustMiniaturizeButton:)
76                   name:NSViewFrameDidChangeNotification
77                 object:miniaturizeButton_];
78    [center addObserver:self
79               selector:@selector(adjustZoomButton:)
80                   name:NSViewFrameDidChangeNotification
81                 object:zoomButton_];
82    [center addObserver:self
83               selector:@selector(themeDidChangeNotification:)
84                   name:kBrowserThemeDidChangeNotification
85                 object:nil];
86  }
87
88  return self;
89}
90
91- (void)dealloc {
92  [[NSNotificationCenter defaultCenter] removeObserver:self];
93  [super dealloc];
94}
95
96- (void)setWindowController:(NSWindowController*)controller {
97  if (controller == [self windowController]) {
98    return;
99  }
100
101  [super setWindowController:controller];
102
103  BrowserWindowController* browserController
104      = static_cast<BrowserWindowController*>(controller);
105  if ([browserController isKindOfClass:[BrowserWindowController class]]) {
106    hasTabStrip_ = [browserController hasTabStrip];
107  } else {
108    hasTabStrip_ = NO;
109  }
110
111  // Force re-layout of the window buttons by wiggling the size of the frame
112  // view.
113  NSView* frameView = [[self contentView] superview];
114  BOOL frameViewDidAutoresizeSubviews = [frameView autoresizesSubviews];
115  [frameView setAutoresizesSubviews:NO];
116  NSRect oldFrame = [frameView frame];
117  [frameView setFrame:NSZeroRect];
118  [frameView setFrame:oldFrame];
119  [frameView setAutoresizesSubviews:frameViewDidAutoresizeSubviews];
120}
121
122- (void)adjustCloseButton:(NSNotification*)notification {
123  [self adjustButton:[notification object]
124              ofKind:NSWindowCloseButton];
125}
126
127- (void)adjustMiniaturizeButton:(NSNotification*)notification {
128  [self adjustButton:[notification object]
129              ofKind:NSWindowMiniaturizeButton];
130}
131
132- (void)adjustZoomButton:(NSNotification*)notification {
133  [self adjustButton:[notification object]
134              ofKind:NSWindowZoomButton];
135}
136
137- (void)adjustButton:(NSButton*)button
138              ofKind:(NSWindowButton)kind {
139  NSRect buttonFrame = [button frame];
140  NSRect frameViewBounds = [[self frameView] bounds];
141
142  CGFloat xOffset = hasTabStrip_
143      ? kFramedWindowButtonsWithTabStripOffsetFromLeft
144      : kFramedWindowButtonsWithoutTabStripOffsetFromLeft;
145  CGFloat yOffset = hasTabStrip_
146      ? kFramedWindowButtonsWithTabStripOffsetFromTop
147      : kFramedWindowButtonsWithoutTabStripOffsetFromTop;
148  buttonFrame.origin =
149      NSMakePoint(xOffset, (NSHeight(frameViewBounds) -
150                            NSHeight(buttonFrame) - yOffset));
151
152  switch (kind) {
153    case NSWindowZoomButton:
154      buttonFrame.origin.x += NSWidth([miniaturizeButton_ frame]);
155      buttonFrame.origin.x += kFramedWindowButtonsInterButtonSpacing;
156      // fallthrough
157    case NSWindowMiniaturizeButton:
158      buttonFrame.origin.x += NSWidth([closeButton_ frame]);
159      buttonFrame.origin.x += kFramedWindowButtonsInterButtonSpacing;
160      // fallthrough
161    default:
162      break;
163  }
164
165  BOOL didPost = [button postsBoundsChangedNotifications];
166  [button setPostsFrameChangedNotifications:NO];
167  [button setFrame:buttonFrame];
168  [button setPostsFrameChangedNotifications:didPost];
169}
170
171- (NSView*)frameView {
172  return [[self contentView] superview];
173}
174
175// The tab strip view covers our window buttons. So we add hit testing here
176// to find them properly and return them to the accessibility system.
177- (id)accessibilityHitTest:(NSPoint)point {
178  NSPoint windowPoint = [self convertScreenToBase:point];
179  NSControl* controls[] = { closeButton_, zoomButton_, miniaturizeButton_ };
180  id value = nil;
181  for (size_t i = 0; i < sizeof(controls) / sizeof(controls[0]); ++i) {
182    if (NSPointInRect(windowPoint, [controls[i] frame])) {
183      value = [controls[i] accessibilityHitTest:point];
184      break;
185    }
186  }
187  if (!value) {
188    value = [super accessibilityHitTest:point];
189  }
190  return value;
191}
192
193- (void)windowMainStatusChanged {
194  NSView* frameView = [self frameView];
195  NSView* contentView = [self contentView];
196  NSRect updateRect = [frameView frame];
197  NSRect contentRect = [contentView frame];
198  CGFloat tabStripHeight = [TabStripController defaultTabHeight];
199  updateRect.size.height -= NSHeight(contentRect) - tabStripHeight;
200  updateRect.origin.y = NSMaxY(contentRect) - tabStripHeight;
201  [[self frameView] setNeedsDisplayInRect:updateRect];
202}
203
204- (void)becomeMainWindow {
205  [self windowMainStatusChanged];
206  [super becomeMainWindow];
207}
208
209- (void)resignMainWindow {
210  [self windowMainStatusChanged];
211  [super resignMainWindow];
212}
213
214// Called after the current theme has changed.
215- (void)themeDidChangeNotification:(NSNotification*)aNotification {
216  [[self frameView] setNeedsDisplay:YES];
217}
218
219- (void)sendEvent:(NSEvent*)event {
220  // For Cocoa windows, clicking on the close and the miniaturize buttons (but
221  // not the zoom button) while a window is in the background does NOT bring
222  // that window to the front. We don't get that behavior for free (probably
223  // because the tab strip view covers those buttons), so we handle it here.
224  // Zoom buttons do bring the window to the front. Note that Finder windows (in
225  // Leopard) behave differently in this regard in that zoom buttons don't bring
226  // the window to the foreground.
227  BOOL eventHandled = NO;
228  if (![self isMainWindow]) {
229    if ([event type] == NSLeftMouseDown) {
230      NSView* frameView = [self frameView];
231      NSPoint mouse = [frameView convertPoint:[event locationInWindow]
232                                     fromView:nil];
233      if (NSPointInRect(mouse, [closeButton_ frame])) {
234        [closeButton_ mouseDown:event];
235        eventHandled = YES;
236      } else if (NSPointInRect(mouse, [miniaturizeButton_ frame])) {
237        [miniaturizeButton_ mouseDown:event];
238        eventHandled = YES;
239      }
240    }
241  }
242  if (!eventHandled) {
243    [super sendEvent:event];
244  }
245}
246
247- (void)setShouldHideTitle:(BOOL)flag {
248  shouldHideTitle_ = flag;
249}
250
251- (BOOL)_isTitleHidden {
252  return shouldHideTitle_;
253}
254
255// This method is called whenever a window is moved in order to ensure it fits
256// on the screen.  We cannot always handle resizes without breaking, so we
257// prevent frame constraining in those cases.
258- (NSRect)constrainFrameRect:(NSRect)frame toScreen:(NSScreen*)screen {
259  // Do not constrain the frame rect if our delegate says no.  In this case,
260  // return the original (unconstrained) frame.
261  id delegate = [self delegate];
262  if ([delegate respondsToSelector:@selector(shouldConstrainFrameRect)] &&
263      ![delegate shouldConstrainFrameRect])
264    return frame;
265
266  return [super constrainFrameRect:frame toScreen:screen];
267}
268
269@end
270