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 23 namespace { 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. 27 const 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