Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / framed_browser_window.mm
bloba83bc148bea819e70db4441d2d2903bccc92de7f
1 // Copyright (c) 2012 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.
5 #import "chrome/browser/ui/cocoa/framed_browser_window.h"
7 #include "base/logging.h"
8 #include "base/mac/sdk_forward_declarations.h"
9 #include "chrome/browser/global_keyboard_shortcuts_mac.h"
10 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
11 #include "chrome/browser/themes/theme_properties.h"
12 #include "chrome/browser/themes/theme_service.h"
13 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
14 #import "chrome/browser/ui/cocoa/browser_window_utils.h"
15 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
16 #import "chrome/browser/ui/cocoa/themed_window.h"
17 #include "grit/theme_resources.h"
18 #include "ui/base/cocoa/nsgraphics_context_additions.h"
19 #import "ui/base/cocoa/nsview_additions.h"
21 // Implementer's note: Moving the window controls is tricky. When altering the
22 // code, ensure that:
23 // - accessibility hit testing works
24 // - the accessibility hierarchy is correct
25 // - close/min in the background don't bring the window forward
26 // - rollover effects work correctly
28 namespace {
30 // Size of the gradient. Empirically determined so that the gradient looks
31 // like what the heuristic does when there are just a few tabs.
32 const CGFloat kWindowGradientHeight = 24.0;
36 @interface FramedBrowserWindow (Private)
38 - (void)adjustCloseButton:(NSNotification*)notification;
39 - (void)adjustMiniaturizeButton:(NSNotification*)notification;
40 - (void)adjustZoomButton:(NSNotification*)notification;
41 - (void)adjustButton:(NSButton*)button
42               ofKind:(NSWindowButton)kind;
44 @end
46 @implementation FramedBrowserWindow
48 - (void)setStyleMask:(NSUInteger)styleMask {
49   if (frameAndStyleMaskLock_)
50     return;
51   [super setStyleMask:styleMask];
54 - (void)setFrame:(NSRect)windowFrame
55          display:(BOOL)displayViews
56          animate:(BOOL)performAnimation {
57   if (frameAndStyleMaskLock_)
58     return;
59   [super setFrame:windowFrame display:displayViews animate:performAnimation];
62 - (id)initWithContentRect:(NSRect)contentRect
63               hasTabStrip:(BOOL)hasTabStrip{
64   NSUInteger styleMask = NSTitledWindowMask |
65                          NSClosableWindowMask |
66                          NSMiniaturizableWindowMask |
67                          NSResizableWindowMask |
68                          NSTexturedBackgroundWindowMask;
69   if ((self = [super initWithContentRect:contentRect
70                                styleMask:styleMask
71                                  backing:NSBackingStoreBuffered
72                                    defer:YES
73                   wantsViewsOverTitlebar:hasTabStrip])) {
74     // The 10.6 fullscreen code copies the title to a different window, which
75     // will assert if it's nil.
76     [self setTitle:@""];
78     // The following two calls fix http://crbug.com/25684 by preventing the
79     // window from recalculating the border thickness as the window is
80     // resized.
81     // This was causing the window tint to change for the default system theme
82     // when the window was being resized.
83     [self setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
84     [self setContentBorderThickness:kWindowGradientHeight forEdge:NSMaxYEdge];
86     hasTabStrip_ = hasTabStrip;
87     closeButton_ = [self standardWindowButton:NSWindowCloseButton];
88     [closeButton_ setPostsFrameChangedNotifications:YES];
89     miniaturizeButton_ = [self standardWindowButton:NSWindowMiniaturizeButton];
90     [miniaturizeButton_ setPostsFrameChangedNotifications:YES];
91     zoomButton_ = [self standardWindowButton:NSWindowZoomButton];
92     [zoomButton_ setPostsFrameChangedNotifications:YES];
94     windowButtonsInterButtonSpacing_ =
95         NSMinX([miniaturizeButton_ frame]) - NSMaxX([closeButton_ frame]);
97     [self adjustButton:closeButton_ ofKind:NSWindowCloseButton];
98     [self adjustButton:miniaturizeButton_ ofKind:NSWindowMiniaturizeButton];
99     [self adjustButton:zoomButton_ ofKind:NSWindowZoomButton];
101     NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
102     [center addObserver:self
103                selector:@selector(adjustCloseButton:)
104                    name:NSViewFrameDidChangeNotification
105                  object:closeButton_];
106     [center addObserver:self
107                selector:@selector(adjustMiniaturizeButton:)
108                    name:NSViewFrameDidChangeNotification
109                  object:miniaturizeButton_];
110     [center addObserver:self
111                selector:@selector(adjustZoomButton:)
112                    name:NSViewFrameDidChangeNotification
113                  object:zoomButton_];
115     frameAndStyleMaskLock_ = NO;
116   }
118   return self;
121 - (void)dealloc {
122   [[NSNotificationCenter defaultCenter] removeObserver:self];
123   [super dealloc];
126 - (void)adjustCloseButton:(NSNotification*)notification {
127   [self adjustButton:[notification object]
128               ofKind:NSWindowCloseButton];
131 - (void)adjustMiniaturizeButton:(NSNotification*)notification {
132   [self adjustButton:[notification object]
133               ofKind:NSWindowMiniaturizeButton];
136 - (void)adjustZoomButton:(NSNotification*)notification {
137   [self adjustButton:[notification object]
138               ofKind:NSWindowZoomButton];
141 - (void)adjustButton:(NSButton*)button
142               ofKind:(NSWindowButton)kind {
143   NSRect buttonFrame = [button frame];
145   CGFloat xOffset = hasTabStrip_
146       ? kFramedWindowButtonsWithTabStripOffsetFromLeft
147       : kFramedWindowButtonsWithoutTabStripOffsetFromLeft;
148   CGFloat yOffset = hasTabStrip_
149       ? kFramedWindowButtonsWithTabStripOffsetFromTop
150       : kFramedWindowButtonsWithoutTabStripOffsetFromTop;
151   buttonFrame.origin =
152       NSMakePoint(xOffset, (NSHeight([self frame]) -
153                             NSHeight(buttonFrame) - yOffset));
155   switch (kind) {
156     case NSWindowZoomButton:
157       buttonFrame.origin.x += NSWidth([miniaturizeButton_ frame]);
158       buttonFrame.origin.x += windowButtonsInterButtonSpacing_;
159       // fallthrough
160     case NSWindowMiniaturizeButton:
161       buttonFrame.origin.x += NSWidth([closeButton_ frame]);
162       buttonFrame.origin.x += windowButtonsInterButtonSpacing_;
163       // fallthrough
164     default:
165       break;
166   }
168   BOOL didPost = [button postsBoundsChangedNotifications];
169   [button setPostsFrameChangedNotifications:NO];
170   [button setFrame:buttonFrame];
171   [button setPostsFrameChangedNotifications:didPost];
174 // The tab strip view covers our window buttons. So we add hit testing here
175 // to find them properly and return them to the accessibility system.
176 - (id)accessibilityHitTest:(NSPoint)point {
177   NSPoint windowPoint = [self convertScreenToBase:point];
178   NSControl* controls[] = { closeButton_, zoomButton_, miniaturizeButton_ };
179   id value = nil;
180   for (size_t i = 0; i < sizeof(controls) / sizeof(controls[0]); ++i) {
181     if (NSPointInRect(windowPoint, [controls[i] frame])) {
182       value = [controls[i] accessibilityHitTest:point];
183       break;
184     }
185   }
186   if (!value) {
187     value = [super accessibilityHitTest:point];
188   }
189   return value;
192 - (void)setShouldHideTitle:(BOOL)flag {
193   shouldHideTitle_ = flag;
196 - (void)setFrameAndStyleMaskLock:(BOOL)lock {
197   frameAndStyleMaskLock_ = lock;
200 - (BOOL)_isTitleHidden {
201   return shouldHideTitle_;
204 - (CGFloat)windowButtonsInterButtonSpacing {
205   return windowButtonsInterButtonSpacing_;
208 // This method is called whenever a window is moved in order to ensure it fits
209 // on the screen.  We cannot always handle resizes without breaking, so we
210 // prevent frame constraining in those cases.
211 - (NSRect)constrainFrameRect:(NSRect)frame toScreen:(NSScreen*)screen {
212   // Do not constrain the frame rect if our delegate says no.  In this case,
213   // return the original (unconstrained) frame.
214   id delegate = [self delegate];
215   if ([delegate respondsToSelector:@selector(shouldConstrainFrameRect)] &&
216       ![delegate shouldConstrainFrameRect])
217     return frame;
219   return [super constrainFrameRect:frame toScreen:screen];
222 // This method is overridden in order to send the toggle fullscreen message
223 // through the cross-platform browser framework before going fullscreen.  The
224 // message will eventually come back as a call to |-toggleSystemFullScreen|,
225 // which in turn calls AppKit's |NSWindow -toggleFullScreen:|.
226 - (void)toggleFullScreen:(id)sender {
227   id delegate = [self delegate];
228   if ([delegate respondsToSelector:@selector(handleLionToggleFullscreen)])
229     [delegate handleLionToggleFullscreen];
232 - (void)toggleSystemFullScreen {
233   if ([super respondsToSelector:@selector(toggleFullScreen:)])
234     [super toggleFullScreen:nil];
237 - (NSPoint)fullScreenButtonOriginAdjustment {
238   if (!hasTabStrip_)
239     return NSZeroPoint;
241   // Vertically center the button.
242   NSPoint origin = NSMakePoint(0, -6);
244   // If there is a profile avatar icon present, shift the button over by its
245   // width and some padding. The new avatar button is displayed to the right
246   // of the fullscreen icon, so it doesn't need to be shifted.
247   BrowserWindowController* bwc =
248       static_cast<BrowserWindowController*>([self windowController]);
249   if ([bwc shouldShowAvatar] && ![bwc shouldUseNewAvatarButton]) {
250     NSView* avatarButton = [[bwc avatarButtonController] view];
251     origin.x = -(NSWidth([avatarButton frame]) + 3);
252   } else {
253     origin.x -= 6;
254   }
256   return origin;
259 + (BOOL)drawWindowThemeInDirtyRect:(NSRect)dirtyRect
260                            forView:(NSView*)view
261                             bounds:(NSRect)bounds
262               forceBlackBackground:(BOOL)forceBlackBackground {
263   ui::ThemeProvider* themeProvider = [[view window] themeProvider];
264   if (!themeProvider)
265     return NO;
267   ThemedWindowStyle windowStyle = [[view window] themedWindowStyle];
269   // Devtools windows don't get themed.
270   if (windowStyle & THEMED_DEVTOOLS)
271     return NO;
273   BOOL active = [[view window] isMainWindow];
274   BOOL incognito = windowStyle & THEMED_INCOGNITO;
275   BOOL popup = windowStyle & THEMED_POPUP;
277   // Find a theme image.
278   NSColor* themeImageColor = nil;
279   if (!popup) {
280     int themeImageID;
281     if (active && incognito)
282       themeImageID = IDR_THEME_FRAME_INCOGNITO;
283     else if (active && !incognito)
284       themeImageID = IDR_THEME_FRAME;
285     else if (!active && incognito)
286       themeImageID = IDR_THEME_FRAME_INCOGNITO_INACTIVE;
287     else
288       themeImageID = IDR_THEME_FRAME_INACTIVE;
289     if (themeProvider->HasCustomImage(IDR_THEME_FRAME))
290       themeImageColor = themeProvider->GetNSImageColorNamed(themeImageID);
291   }
293   // If no theme image, use a gradient if incognito.
294   NSGradient* gradient = nil;
295   if (!themeImageColor && incognito)
296     gradient = themeProvider->GetNSGradient(
297         active ? ThemeProperties::GRADIENT_FRAME_INCOGNITO :
298                  ThemeProperties::GRADIENT_FRAME_INCOGNITO_INACTIVE);
300   BOOL themed = NO;
301   if (themeImageColor) {
302     // Default to replacing any existing pixels with the theme image, but if
303     // asked paint black first and blend the theme with black.
304     NSCompositingOperation operation = NSCompositeCopy;
305     if (forceBlackBackground) {
306       [[NSColor blackColor] set];
307       NSRectFill(dirtyRect);
308       operation = NSCompositeSourceOver;
309     }
311     NSPoint position = [[view window] themeImagePositionForAlignment:
312         THEME_IMAGE_ALIGN_WITH_FRAME];
313     [[NSGraphicsContext currentContext] cr_setPatternPhase:position
314                                                    forView:view];
316     [themeImageColor set];
317     NSRectFillUsingOperation(dirtyRect, operation);
318     themed = YES;
319   } else if (gradient) {
320     NSPoint startPoint = NSMakePoint(NSMinX(bounds), NSMaxY(bounds));
321     NSPoint endPoint = startPoint;
322     endPoint.y -= kBrowserFrameViewPaintHeight;
323     [gradient drawFromPoint:startPoint toPoint:endPoint options:0];
324     themed = YES;
325   }
327   // Check to see if we have an overlay image.
328   NSImage* overlayImage = nil;
329   if (themeProvider->HasCustomImage(IDR_THEME_FRAME_OVERLAY) && !incognito &&
330       !popup) {
331     overlayImage = themeProvider->
332         GetNSImageNamed(active ? IDR_THEME_FRAME_OVERLAY :
333                                  IDR_THEME_FRAME_OVERLAY_INACTIVE);
334   }
336   if (overlayImage) {
337     // Anchor to top-left and don't scale.
338     NSPoint position = [[view window] themeImagePositionForAlignment:
339         THEME_IMAGE_ALIGN_WITH_FRAME];
340     position = [view convertPoint:position fromView:nil];
341     NSSize overlaySize = [overlayImage size];
342     NSRect imageFrame = NSMakeRect(0, 0, overlaySize.width, overlaySize.height);
343     [overlayImage drawAtPoint:NSMakePoint(position.x,
344                                           position.y - overlaySize.height)
345                      fromRect:imageFrame
346                     operation:NSCompositeSourceOver
347                      fraction:1.0];
348   }
350   return themed;
353 - (NSColor*)titleColor {
354   ui::ThemeProvider* themeProvider = [self themeProvider];
355   if (!themeProvider)
356     return [NSColor windowFrameTextColor];
358   ThemedWindowStyle windowStyle = [self themedWindowStyle];
359   BOOL incognito = windowStyle & THEMED_INCOGNITO;
361   if (incognito)
362     return [NSColor whiteColor];
363   else
364     return [NSColor windowFrameTextColor];
367 @end