framed_browser_window.mm revision 3f50c38dc070f4bb515c1b64450dae14f316474e
1// Copyright (c) 2010 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/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/browser_theme_provider.h" 15 16namespace { 17 // Size of the gradient. Empirically determined so that the gradient looks 18 // like what the heuristic does when there are just a few tabs. 19 const CGFloat kWindowGradientHeight = 24.0; 20} 21 22// Our browser window does some interesting things to get the behaviors that 23// we want. We replace the standard window controls (zoom, close, miniaturize) 24// with our own versions, so that we can position them slightly differently than 25// the default window has them. To do this, we hide the ones that Apple provides 26// us with, and create our own. This requires us to handle tracking for the 27// buttons (so that they highlight and activate correctly) as well as implement 28// the private method _mouseInGroup in our frame view class which is required 29// to get the rollover highlight drawing to draw correctly. 30@interface FramedBrowserWindow(PrivateMethods) 31// Return the view that does the "frame" drawing. 32- (NSView*)frameView; 33@end 34 35@implementation FramedBrowserWindow 36 37- (id)initWithContentRect:(NSRect)contentRect 38 styleMask:(NSUInteger)aStyle 39 backing:(NSBackingStoreType)bufferingType 40 defer:(BOOL)flag { 41 if ((self = [super initWithContentRect:contentRect 42 styleMask:aStyle 43 backing:bufferingType 44 defer:flag])) { 45 if (aStyle & NSTexturedBackgroundWindowMask) { 46 // The following two calls fix http://www.crbug.com/25684 by preventing 47 // the window from recalculating the border thickness as the window is 48 // resized. 49 // This was causing the window tint to change for the default system theme 50 // when the window was being resized. 51 [self setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge]; 52 [self setContentBorderThickness:kWindowGradientHeight forEdge:NSMaxYEdge]; 53 } 54 } 55 return self; 56} 57 58- (void)dealloc { 59 [[NSNotificationCenter defaultCenter] removeObserver:self]; 60 [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; 61 if (widgetTrackingArea_) { 62 [[self frameView] removeTrackingArea:widgetTrackingArea_]; 63 widgetTrackingArea_.reset(); 64 } 65 [super dealloc]; 66} 67 68- (void)setWindowController:(NSWindowController*)controller { 69 if (controller == [self windowController]) { 70 return; 71 } 72 // Clean up our old stuff. 73 [[NSNotificationCenter defaultCenter] removeObserver:self]; 74 [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; 75 [closeButton_ removeFromSuperview]; 76 closeButton_ = nil; 77 [miniaturizeButton_ removeFromSuperview]; 78 miniaturizeButton_ = nil; 79 [zoomButton_ removeFromSuperview]; 80 zoomButton_ = nil; 81 82 [super setWindowController:controller]; 83 84 BrowserWindowController* browserController 85 = static_cast<BrowserWindowController*>(controller); 86 if ([browserController isKindOfClass:[BrowserWindowController class]]) { 87 NSNotificationCenter* defaultCenter = [NSNotificationCenter defaultCenter]; 88 [defaultCenter addObserver:self 89 selector:@selector(themeDidChangeNotification:) 90 name:kBrowserThemeDidChangeNotification 91 object:nil]; 92 93 // Hook ourselves up to get notified if the user changes the system 94 // theme on us. 95 NSDistributedNotificationCenter* distCenter = 96 [NSDistributedNotificationCenter defaultCenter]; 97 [distCenter addObserver:self 98 selector:@selector(systemThemeDidChangeNotification:) 99 name:@"AppleAquaColorVariantChanged" 100 object:nil]; 101 // Set up our buttons how we like them. 102 NSView* frameView = [self frameView]; 103 NSRect frameViewBounds = [frameView bounds]; 104 105 // Find all the "original" buttons, and hide them. We can't use the original 106 // buttons because the OS likes to move them around when we resize windows 107 // and will put them back in what it considers to be their "preferred" 108 // locations. 109 NSButton* oldButton = [self standardWindowButton:NSWindowCloseButton]; 110 [oldButton setHidden:YES]; 111 oldButton = [self standardWindowButton:NSWindowMiniaturizeButton]; 112 [oldButton setHidden:YES]; 113 oldButton = [self standardWindowButton:NSWindowZoomButton]; 114 [oldButton setHidden:YES]; 115 116 // Create and position our new buttons. 117 NSUInteger aStyle = [self styleMask]; 118 closeButton_ = [NSWindow standardWindowButton:NSWindowCloseButton 119 forStyleMask:aStyle]; 120 NSRect closeButtonFrame = [closeButton_ frame]; 121 BOOL hasTabStrip = [browserController hasTabStrip]; 122 CGFloat xOffset = hasTabStrip ? 123 kFramedWindowButtonsWithTabStripOffsetFromLeft : 124 kFramedWindowButtonsWithoutTabStripOffsetFromLeft; 125 CGFloat yOffset = hasTabStrip ? 126 kFramedWindowButtonsWithTabStripOffsetFromTop : 127 kFramedWindowButtonsWithoutTabStripOffsetFromTop; 128 closeButtonFrame.origin = 129 NSMakePoint(xOffset, (NSHeight(frameViewBounds) - 130 NSHeight(closeButtonFrame) - yOffset)); 131 132 [closeButton_ setFrame:closeButtonFrame]; 133 [closeButton_ setTarget:self]; 134 [closeButton_ setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin]; 135 [frameView addSubview:closeButton_]; 136 137 miniaturizeButton_ = 138 [NSWindow standardWindowButton:NSWindowMiniaturizeButton 139 forStyleMask:aStyle]; 140 NSRect miniaturizeButtonFrame = [miniaturizeButton_ frame]; 141 miniaturizeButtonFrame.origin = 142 NSMakePoint((NSMaxX(closeButtonFrame) + 143 kFramedWindowButtonsInterButtonSpacing), 144 NSMinY(closeButtonFrame)); 145 [miniaturizeButton_ setFrame:miniaturizeButtonFrame]; 146 [miniaturizeButton_ setTarget:self]; 147 [miniaturizeButton_ setAutoresizingMask:(NSViewMaxXMargin | 148 NSViewMinYMargin)]; 149 [frameView addSubview:miniaturizeButton_]; 150 151 zoomButton_ = [NSWindow standardWindowButton:NSWindowZoomButton 152 forStyleMask:aStyle]; 153 NSRect zoomButtonFrame = [zoomButton_ frame]; 154 zoomButtonFrame.origin = 155 NSMakePoint((NSMaxX(miniaturizeButtonFrame) + 156 kFramedWindowButtonsInterButtonSpacing), 157 NSMinY(miniaturizeButtonFrame)); 158 [zoomButton_ setFrame:zoomButtonFrame]; 159 [zoomButton_ setTarget:self]; 160 [zoomButton_ setAutoresizingMask:(NSViewMaxXMargin | 161 NSViewMinYMargin)]; 162 163 [frameView addSubview:zoomButton_]; 164 } 165 166 // Update our tracking areas. We want to update them even if we haven't 167 // added buttons above as we need to remove the old tracking area. If the 168 // buttons aren't to be shown, updateTrackingAreas won't add new ones. 169 [self updateTrackingAreas]; 170} 171 172- (NSView*)frameView { 173 return [[self contentView] superview]; 174} 175 176// The tab strip view covers our window buttons. So we add hit testing here 177// to find them properly and return them to the accessibility system. 178- (id)accessibilityHitTest:(NSPoint)point { 179 NSPoint windowPoint = [self convertScreenToBase:point]; 180 NSControl* controls[] = { closeButton_, zoomButton_, miniaturizeButton_ }; 181 id value = nil; 182 for (size_t i = 0; i < sizeof(controls) / sizeof(controls[0]); ++i) { 183 if (NSPointInRect(windowPoint, [controls[i] frame])) { 184 value = [controls[i] accessibilityHitTest:point]; 185 break; 186 } 187 } 188 if (!value) { 189 value = [super accessibilityHitTest:point]; 190 } 191 return value; 192} 193 194// Map our custom buttons into the accessibility hierarchy correctly. 195- (id)accessibilityAttributeValue:(NSString*)attribute { 196 id value = nil; 197 struct { 198 NSString* attribute_; 199 id value_; 200 } attributeMap[] = { 201 { NSAccessibilityCloseButtonAttribute, [closeButton_ cell]}, 202 { NSAccessibilityZoomButtonAttribute, [zoomButton_ cell]}, 203 { NSAccessibilityMinimizeButtonAttribute, [miniaturizeButton_ cell]}, 204 }; 205 206 for (size_t i = 0; i < sizeof(attributeMap) / sizeof(attributeMap[0]); ++i) { 207 if ([attributeMap[i].attribute_ isEqualToString:attribute]) { 208 value = attributeMap[i].value_; 209 break; 210 } 211 } 212 if (!value) { 213 value = [super accessibilityAttributeValue:attribute]; 214 } 215 return value; 216} 217 218- (void)updateTrackingAreas { 219 NSView* frameView = [self frameView]; 220 if (widgetTrackingArea_) { 221 [frameView removeTrackingArea:widgetTrackingArea_]; 222 } 223 if (closeButton_) { 224 NSRect trackingRect = [closeButton_ frame]; 225 trackingRect.size.width = NSMaxX([zoomButton_ frame]) - 226 NSMinX(trackingRect); 227 widgetTrackingArea_.reset( 228 [[NSTrackingArea alloc] initWithRect:trackingRect 229 options:(NSTrackingMouseEnteredAndExited | 230 NSTrackingActiveAlways) 231 owner:self 232 userInfo:nil]); 233 [frameView addTrackingArea:widgetTrackingArea_]; 234 235 // Check to see if the cursor is still in trackingRect. 236 NSPoint point = [self mouseLocationOutsideOfEventStream]; 237 point = [[self contentView] convertPoint:point fromView:nil]; 238 BOOL newEntered = NSPointInRect (point, trackingRect); 239 if (newEntered != entered_) { 240 // Buttons have moved, so update button state. 241 entered_ = newEntered; 242 [closeButton_ setNeedsDisplay]; 243 [zoomButton_ setNeedsDisplay]; 244 [miniaturizeButton_ setNeedsDisplay]; 245 } 246 } 247} 248 249- (void)windowMainStatusChanged { 250 [closeButton_ setNeedsDisplay]; 251 [zoomButton_ setNeedsDisplay]; 252 [miniaturizeButton_ setNeedsDisplay]; 253 NSView* frameView = [self frameView]; 254 NSView* contentView = [self contentView]; 255 NSRect updateRect = [frameView frame]; 256 NSRect contentRect = [contentView frame]; 257 CGFloat tabStripHeight = [TabStripController defaultTabHeight]; 258 updateRect.size.height -= NSHeight(contentRect) - tabStripHeight; 259 updateRect.origin.y = NSMaxY(contentRect) - tabStripHeight; 260 [[self frameView] setNeedsDisplayInRect:updateRect]; 261} 262 263- (void)becomeMainWindow { 264 [self windowMainStatusChanged]; 265 [super becomeMainWindow]; 266} 267 268- (void)resignMainWindow { 269 [self windowMainStatusChanged]; 270 [super resignMainWindow]; 271} 272 273// Called after the current theme has changed. 274- (void)themeDidChangeNotification:(NSNotification*)aNotification { 275 [[self frameView] setNeedsDisplay:YES]; 276} 277 278- (void)systemThemeDidChangeNotification:(NSNotification*)aNotification { 279 [closeButton_ setNeedsDisplay]; 280 [zoomButton_ setNeedsDisplay]; 281 [miniaturizeButton_ setNeedsDisplay]; 282} 283 284- (void)sendEvent:(NSEvent*)event { 285 // For cocoa windows, clicking on the close and the miniaturize (but not the 286 // zoom buttons) while a window is in the background does NOT bring that 287 // window to the front. We don't get that behavior for free, so we handle 288 // it here. Zoom buttons do bring the window to the front. Note that 289 // Finder windows (in Leopard) behave differently in this regard in that 290 // zoom buttons don't bring the window to the foreground. 291 BOOL eventHandled = NO; 292 if (![self isMainWindow]) { 293 if ([event type] == NSLeftMouseDown) { 294 NSView* frameView = [self frameView]; 295 NSPoint mouse = [frameView convertPoint:[event locationInWindow] 296 fromView:nil]; 297 if (NSPointInRect(mouse, [closeButton_ frame])) { 298 [closeButton_ mouseDown:event]; 299 eventHandled = YES; 300 } else if (NSPointInRect(mouse, [miniaturizeButton_ frame])) { 301 [miniaturizeButton_ mouseDown:event]; 302 eventHandled = YES; 303 } 304 } 305 } 306 if (!eventHandled) { 307 [super sendEvent:event]; 308 } 309} 310 311// Update our buttons so that they highlight correctly. 312- (void)mouseEntered:(NSEvent*)event { 313 entered_ = YES; 314 [closeButton_ setNeedsDisplay]; 315 [zoomButton_ setNeedsDisplay]; 316 [miniaturizeButton_ setNeedsDisplay]; 317} 318 319// Update our buttons so that they highlight correctly. 320- (void)mouseExited:(NSEvent*)event { 321 entered_ = NO; 322 [closeButton_ setNeedsDisplay]; 323 [zoomButton_ setNeedsDisplay]; 324 [miniaturizeButton_ setNeedsDisplay]; 325} 326 327- (BOOL)mouseInGroup:(NSButton*)widget { 328 return entered_; 329} 330 331- (void)setShouldHideTitle:(BOOL)flag { 332 shouldHideTitle_ = flag; 333} 334 335-(BOOL)_isTitleHidden { 336 return shouldHideTitle_; 337} 338 339// This method is called whenever a window is moved in order to ensure it fits 340// on the screen. We cannot always handle resizes without breaking, so we 341// prevent frame constraining in those cases. 342- (NSRect)constrainFrameRect:(NSRect)frame toScreen:(NSScreen*)screen { 343 // Do not constrain the frame rect if our delegate says no. In this case, 344 // return the original (unconstrained) frame. 345 id delegate = [self delegate]; 346 if ([delegate respondsToSelector:@selector(shouldConstrainFrameRect)] && 347 ![delegate shouldConstrainFrameRect]) 348 return frame; 349 350 return [super constrainFrameRect:frame toScreen:screen]; 351} 352 353@end 354