Home | History | Annotate | Download | only in cocoa
      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