| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "chrome/browser/ui/cocoa/framed_browser_window.h" |
| |
| #include "base/logging.h" |
| #include "chrome/browser/global_keyboard_shortcuts_mac.h" |
| #import "chrome/browser/ui/cocoa/browser_frame_view.h" |
| #import "chrome/browser/ui/cocoa/browser_window_controller.h" |
| #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h" |
| #import "chrome/browser/ui/cocoa/themed_window.h" |
| #include "chrome/browser/themes/theme_service.h" |
| |
| // Replicate specific 10.7 SDK declarations for building with prior SDKs. |
| #if !defined(MAC_OS_X_VERSION_10_7) || \ |
| MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7 |
| |
| @interface NSWindow (LionSDKDeclarations) |
| - (void)toggleFullScreen:(id)sender; |
| @end |
| |
| #endif // MAC_OS_X_VERSION_10_7 |
| |
| |
| // Implementer's note: Moving the window controls is tricky. When altering the |
| // code, ensure that: |
| // - accessibility hit testing works |
| // - the accessibility hierarchy is correct |
| // - close/min in the background don't bring the window forward |
| // - rollover effects work correctly |
| |
| namespace { |
| |
| // Size of the gradient. Empirically determined so that the gradient looks |
| // like what the heuristic does when there are just a few tabs. |
| const CGFloat kWindowGradientHeight = 24.0; |
| |
| } |
| |
| @interface FramedBrowserWindow (Private) |
| |
| - (void)adjustCloseButton:(NSNotification*)notification; |
| - (void)adjustMiniaturizeButton:(NSNotification*)notification; |
| - (void)adjustZoomButton:(NSNotification*)notification; |
| - (void)adjustButton:(NSButton*)button |
| ofKind:(NSWindowButton)kind; |
| - (NSView*)frameView; |
| |
| @end |
| |
| @implementation FramedBrowserWindow |
| |
| - (id)initWithContentRect:(NSRect)contentRect |
| styleMask:(NSUInteger)aStyle |
| backing:(NSBackingStoreType)bufferingType |
| defer:(BOOL)flag { |
| if ((self = [super initWithContentRect:contentRect |
| styleMask:aStyle |
| backing:bufferingType |
| defer:flag])) { |
| if (aStyle & NSTexturedBackgroundWindowMask) { |
| // The following two calls fix http://www.crbug.com/25684 by preventing |
| // the window from recalculating the border thickness as the window is |
| // resized. |
| // This was causing the window tint to change for the default system theme |
| // when the window was being resized. |
| [self setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge]; |
| [self setContentBorderThickness:kWindowGradientHeight forEdge:NSMaxYEdge]; |
| } |
| |
| closeButton_ = [self standardWindowButton:NSWindowCloseButton]; |
| [closeButton_ setPostsFrameChangedNotifications:YES]; |
| miniaturizeButton_ = [self standardWindowButton:NSWindowMiniaturizeButton]; |
| [miniaturizeButton_ setPostsFrameChangedNotifications:YES]; |
| zoomButton_ = [self standardWindowButton:NSWindowZoomButton]; |
| [zoomButton_ setPostsFrameChangedNotifications:YES]; |
| |
| windowButtonsInterButtonSpacing_ = |
| NSMinX([miniaturizeButton_ frame]) - NSMaxX([closeButton_ frame]); |
| |
| NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; |
| [center addObserver:self |
| selector:@selector(adjustCloseButton:) |
| name:NSViewFrameDidChangeNotification |
| object:closeButton_]; |
| [center addObserver:self |
| selector:@selector(adjustMiniaturizeButton:) |
| name:NSViewFrameDidChangeNotification |
| object:miniaturizeButton_]; |
| [center addObserver:self |
| selector:@selector(adjustZoomButton:) |
| name:NSViewFrameDidChangeNotification |
| object:zoomButton_]; |
| [center addObserver:self |
| selector:@selector(themeDidChangeNotification:) |
| name:kBrowserThemeDidChangeNotification |
| object:nil]; |
| } |
| |
| return self; |
| } |
| |
| - (void)dealloc { |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| [super dealloc]; |
| } |
| |
| - (void)setWindowController:(NSWindowController*)controller { |
| if (controller == [self windowController]) { |
| return; |
| } |
| |
| [super setWindowController:controller]; |
| |
| BrowserWindowController* browserController |
| = static_cast<BrowserWindowController*>(controller); |
| if ([browserController isKindOfClass:[BrowserWindowController class]]) { |
| hasTabStrip_ = [browserController hasTabStrip]; |
| } else { |
| hasTabStrip_ = NO; |
| } |
| |
| // Force re-layout of the window buttons by wiggling the size of the frame |
| // view. |
| NSView* frameView = [[self contentView] superview]; |
| BOOL frameViewDidAutoresizeSubviews = [frameView autoresizesSubviews]; |
| [frameView setAutoresizesSubviews:NO]; |
| NSRect oldFrame = [frameView frame]; |
| [frameView setFrame:NSZeroRect]; |
| [frameView setFrame:oldFrame]; |
| [frameView setAutoresizesSubviews:frameViewDidAutoresizeSubviews]; |
| } |
| |
| - (void)adjustCloseButton:(NSNotification*)notification { |
| [self adjustButton:[notification object] |
| ofKind:NSWindowCloseButton]; |
| } |
| |
| - (void)adjustMiniaturizeButton:(NSNotification*)notification { |
| [self adjustButton:[notification object] |
| ofKind:NSWindowMiniaturizeButton]; |
| } |
| |
| - (void)adjustZoomButton:(NSNotification*)notification { |
| [self adjustButton:[notification object] |
| ofKind:NSWindowZoomButton]; |
| } |
| |
| - (void)adjustButton:(NSButton*)button |
| ofKind:(NSWindowButton)kind { |
| NSRect buttonFrame = [button frame]; |
| NSRect frameViewBounds = [[self frameView] bounds]; |
| |
| CGFloat xOffset = hasTabStrip_ |
| ? kFramedWindowButtonsWithTabStripOffsetFromLeft |
| : kFramedWindowButtonsWithoutTabStripOffsetFromLeft; |
| CGFloat yOffset = hasTabStrip_ |
| ? kFramedWindowButtonsWithTabStripOffsetFromTop |
| : kFramedWindowButtonsWithoutTabStripOffsetFromTop; |
| buttonFrame.origin = |
| NSMakePoint(xOffset, (NSHeight(frameViewBounds) - |
| NSHeight(buttonFrame) - yOffset)); |
| |
| switch (kind) { |
| case NSWindowZoomButton: |
| buttonFrame.origin.x += NSWidth([miniaturizeButton_ frame]); |
| buttonFrame.origin.x += windowButtonsInterButtonSpacing_; |
| // fallthrough |
| case NSWindowMiniaturizeButton: |
| buttonFrame.origin.x += NSWidth([closeButton_ frame]); |
| buttonFrame.origin.x += windowButtonsInterButtonSpacing_; |
| // fallthrough |
| default: |
| break; |
| } |
| |
| BOOL didPost = [button postsBoundsChangedNotifications]; |
| [button setPostsFrameChangedNotifications:NO]; |
| [button setFrame:buttonFrame]; |
| [button setPostsFrameChangedNotifications:didPost]; |
| } |
| |
| - (NSView*)frameView { |
| return [[self contentView] superview]; |
| } |
| |
| // The tab strip view covers our window buttons. So we add hit testing here |
| // to find them properly and return them to the accessibility system. |
| - (id)accessibilityHitTest:(NSPoint)point { |
| NSPoint windowPoint = [self convertScreenToBase:point]; |
| NSControl* controls[] = { closeButton_, zoomButton_, miniaturizeButton_ }; |
| id value = nil; |
| for (size_t i = 0; i < sizeof(controls) / sizeof(controls[0]); ++i) { |
| if (NSPointInRect(windowPoint, [controls[i] frame])) { |
| value = [controls[i] accessibilityHitTest:point]; |
| break; |
| } |
| } |
| if (!value) { |
| value = [super accessibilityHitTest:point]; |
| } |
| return value; |
| } |
| |
| - (void)windowMainStatusChanged { |
| NSView* frameView = [self frameView]; |
| NSView* contentView = [self contentView]; |
| NSRect updateRect = [frameView frame]; |
| NSRect contentRect = [contentView frame]; |
| CGFloat tabStripHeight = [TabStripController defaultTabHeight]; |
| updateRect.size.height -= NSHeight(contentRect) - tabStripHeight; |
| updateRect.origin.y = NSMaxY(contentRect) - tabStripHeight; |
| [[self frameView] setNeedsDisplayInRect:updateRect]; |
| } |
| |
| - (void)becomeMainWindow { |
| [self windowMainStatusChanged]; |
| [super becomeMainWindow]; |
| } |
| |
| - (void)resignMainWindow { |
| [self windowMainStatusChanged]; |
| [super resignMainWindow]; |
| } |
| |
| // Called after the current theme has changed. |
| - (void)themeDidChangeNotification:(NSNotification*)aNotification { |
| [[self frameView] setNeedsDisplay:YES]; |
| } |
| |
| - (void)sendEvent:(NSEvent*)event { |
| // For Cocoa windows, clicking on the close and the miniaturize buttons (but |
| // not the zoom button) while a window is in the background does NOT bring |
| // that window to the front. We don't get that behavior for free (probably |
| // because the tab strip view covers those buttons), so we handle it here. |
| // Zoom buttons do bring the window to the front. Note that Finder windows (in |
| // Leopard) behave differently in this regard in that zoom buttons don't bring |
| // the window to the foreground. |
| BOOL eventHandled = NO; |
| if (![self isMainWindow]) { |
| if ([event type] == NSLeftMouseDown) { |
| NSView* frameView = [self frameView]; |
| NSPoint mouse = [frameView convertPoint:[event locationInWindow] |
| fromView:nil]; |
| if (NSPointInRect(mouse, [closeButton_ frame])) { |
| [closeButton_ mouseDown:event]; |
| eventHandled = YES; |
| } else if (NSPointInRect(mouse, [miniaturizeButton_ frame])) { |
| [miniaturizeButton_ mouseDown:event]; |
| eventHandled = YES; |
| } |
| } |
| } |
| if (!eventHandled) { |
| [super sendEvent:event]; |
| } |
| } |
| |
| - (void)setShouldHideTitle:(BOOL)flag { |
| shouldHideTitle_ = flag; |
| } |
| |
| - (BOOL)_isTitleHidden { |
| return shouldHideTitle_; |
| } |
| |
| - (CGFloat)windowButtonsInterButtonSpacing { |
| return windowButtonsInterButtonSpacing_; |
| } |
| |
| // This method is called whenever a window is moved in order to ensure it fits |
| // on the screen. We cannot always handle resizes without breaking, so we |
| // prevent frame constraining in those cases. |
| - (NSRect)constrainFrameRect:(NSRect)frame toScreen:(NSScreen*)screen { |
| // Do not constrain the frame rect if our delegate says no. In this case, |
| // return the original (unconstrained) frame. |
| id delegate = [self delegate]; |
| if ([delegate respondsToSelector:@selector(shouldConstrainFrameRect)] && |
| ![delegate shouldConstrainFrameRect]) |
| return frame; |
| |
| return [super constrainFrameRect:frame toScreen:screen]; |
| } |
| |
| // This method is overridden in order to send the toggle fullscreen message |
| // through the cross-platform browser framework before going fullscreen. The |
| // message will eventually come back as a call to |-toggleSystemFullScreen|, |
| // which in turn calls AppKit's |NSWindow -toggleFullScreen:|. |
| - (void)toggleFullScreen:(id)sender { |
| id delegate = [self delegate]; |
| if ([delegate respondsToSelector:@selector(handleLionToggleFullscreen)]) |
| [delegate handleLionToggleFullscreen]; |
| } |
| |
| - (void)toggleSystemFullScreen { |
| if ([super respondsToSelector:@selector(toggleFullScreen:)]) |
| [super toggleFullScreen:nil]; |
| } |
| |
| @end |