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