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
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
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;
46 @implementation FramedBrowserWindow
48 - (id)initWithContentRect:(NSRect)contentRect
49 hasTabStrip:(BOOL)hasTabStrip{
50 NSUInteger styleMask = NSTitledWindowMask |
51 NSClosableWindowMask |
52 NSMiniaturizableWindowMask |
53 NSResizableWindowMask |
54 NSTexturedBackgroundWindowMask;
55 if ((self = [super initWithContentRect:contentRect
57 backing:NSBackingStoreBuffered
59 wantsViewsOverTitlebar:hasTabStrip])) {
60 // The 10.6 fullscreen code copies the title to a different window, which
61 // will assert if it's nil.
64 // The following two calls fix http://crbug.com/25684 by preventing the
65 // window from recalculating the border thickness as the window is
67 // This was causing the window tint to change for the default system theme
68 // when the window was being resized.
69 [self setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge];
70 [self setContentBorderThickness:kWindowGradientHeight forEdge:NSMaxYEdge];
72 hasTabStrip_ = hasTabStrip;
73 closeButton_ = [self standardWindowButton:NSWindowCloseButton];
74 [closeButton_ setPostsFrameChangedNotifications:YES];
75 miniaturizeButton_ = [self standardWindowButton:NSWindowMiniaturizeButton];
76 [miniaturizeButton_ setPostsFrameChangedNotifications:YES];
77 zoomButton_ = [self standardWindowButton:NSWindowZoomButton];
78 [zoomButton_ setPostsFrameChangedNotifications:YES];
80 windowButtonsInterButtonSpacing_ =
81 NSMinX([miniaturizeButton_ frame]) - NSMaxX([closeButton_ frame]);
83 [self adjustButton:closeButton_ ofKind:NSWindowCloseButton];
84 [self adjustButton:miniaturizeButton_ ofKind:NSWindowMiniaturizeButton];
85 [self adjustButton:zoomButton_ ofKind:NSWindowZoomButton];
87 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
88 [center addObserver:self
89 selector:@selector(adjustCloseButton:)
90 name:NSViewFrameDidChangeNotification
92 [center addObserver:self
93 selector:@selector(adjustMiniaturizeButton:)
94 name:NSViewFrameDidChangeNotification
95 object:miniaturizeButton_];
96 [center addObserver:self
97 selector:@selector(adjustZoomButton:)
98 name:NSViewFrameDidChangeNotification
106 [[NSNotificationCenter defaultCenter] removeObserver:self];
110 - (void)adjustCloseButton:(NSNotification*)notification {
111 [self adjustButton:[notification object]
112 ofKind:NSWindowCloseButton];
115 - (void)adjustMiniaturizeButton:(NSNotification*)notification {
116 [self adjustButton:[notification object]
117 ofKind:NSWindowMiniaturizeButton];
120 - (void)adjustZoomButton:(NSNotification*)notification {
121 [self adjustButton:[notification object]
122 ofKind:NSWindowZoomButton];
125 - (void)adjustButton:(NSButton*)button
126 ofKind:(NSWindowButton)kind {
127 NSRect buttonFrame = [button frame];
129 CGFloat xOffset = hasTabStrip_
130 ? kFramedWindowButtonsWithTabStripOffsetFromLeft
131 : kFramedWindowButtonsWithoutTabStripOffsetFromLeft;
132 CGFloat yOffset = hasTabStrip_
133 ? kFramedWindowButtonsWithTabStripOffsetFromTop
134 : kFramedWindowButtonsWithoutTabStripOffsetFromTop;
136 NSMakePoint(xOffset, (NSHeight([self frame]) -
137 NSHeight(buttonFrame) - yOffset));
140 case NSWindowZoomButton:
141 buttonFrame.origin.x += NSWidth([miniaturizeButton_ frame]);
142 buttonFrame.origin.x += windowButtonsInterButtonSpacing_;
144 case NSWindowMiniaturizeButton:
145 buttonFrame.origin.x += NSWidth([closeButton_ frame]);
146 buttonFrame.origin.x += windowButtonsInterButtonSpacing_;
152 BOOL didPost = [button postsBoundsChangedNotifications];
153 [button setPostsFrameChangedNotifications:NO];
154 [button setFrame:buttonFrame];
155 [button setPostsFrameChangedNotifications:didPost];
158 // The tab strip view covers our window buttons. So we add hit testing here
159 // to find them properly and return them to the accessibility system.
160 - (id)accessibilityHitTest:(NSPoint)point {
161 NSPoint windowPoint = [self convertScreenToBase:point];
162 NSControl* controls[] = { closeButton_, zoomButton_, miniaturizeButton_ };
164 for (size_t i = 0; i < sizeof(controls) / sizeof(controls[0]); ++i) {
165 if (NSPointInRect(windowPoint, [controls[i] frame])) {
166 value = [controls[i] accessibilityHitTest:point];
171 value = [super accessibilityHitTest:point];
176 - (void)setShouldHideTitle:(BOOL)flag {
177 shouldHideTitle_ = flag;
180 - (BOOL)_isTitleHidden {
181 return shouldHideTitle_;
184 - (CGFloat)windowButtonsInterButtonSpacing {
185 return windowButtonsInterButtonSpacing_;
188 // This method is called whenever a window is moved in order to ensure it fits
189 // on the screen. We cannot always handle resizes without breaking, so we
190 // prevent frame constraining in those cases.
191 - (NSRect)constrainFrameRect:(NSRect)frame toScreen:(NSScreen*)screen {
192 // Do not constrain the frame rect if our delegate says no. In this case,
193 // return the original (unconstrained) frame.
194 id delegate = [self delegate];
195 if ([delegate respondsToSelector:@selector(shouldConstrainFrameRect)] &&
196 ![delegate shouldConstrainFrameRect])
199 return [super constrainFrameRect:frame toScreen:screen];
202 // This method is overridden in order to send the toggle fullscreen message
203 // through the cross-platform browser framework before going fullscreen. The
204 // message will eventually come back as a call to |-toggleSystemFullScreen|,
205 // which in turn calls AppKit's |NSWindow -toggleFullScreen:|.
206 - (void)toggleFullScreen:(id)sender {
207 id delegate = [self delegate];
208 if ([delegate respondsToSelector:@selector(handleLionToggleFullscreen)])
209 [delegate handleLionToggleFullscreen];
212 - (void)toggleSystemFullScreen {
213 if ([super respondsToSelector:@selector(toggleFullScreen:)])
214 [super toggleFullScreen:nil];
217 - (NSPoint)fullScreenButtonOriginAdjustment {
221 // Vertically center the button.
222 NSPoint origin = NSMakePoint(0, -6);
224 // If there is a profile avatar icon present, shift the button over by its
225 // width and some padding. The new avatar button is displayed to the right
226 // of the fullscreen icon, so it doesn't need to be shifted.
227 BrowserWindowController* bwc =
228 static_cast<BrowserWindowController*>([self windowController]);
229 if ([bwc shouldShowAvatar] && ![bwc shouldUseNewAvatarButton]) {
230 NSView* avatarButton = [[bwc avatarButtonController] view];
231 origin.x = -(NSWidth([avatarButton frame]) + 3);
239 + (BOOL)drawWindowThemeInDirtyRect:(NSRect)dirtyRect
240 forView:(NSView*)view
241 bounds:(NSRect)bounds
242 forceBlackBackground:(BOOL)forceBlackBackground {
243 ui::ThemeProvider* themeProvider = [[view window] themeProvider];
247 ThemedWindowStyle windowStyle = [[view window] themedWindowStyle];
249 // Devtools windows don't get themed.
250 if (windowStyle & THEMED_DEVTOOLS)
253 BOOL active = [[view window] isMainWindow];
254 BOOL incognito = windowStyle & THEMED_INCOGNITO;
255 BOOL popup = windowStyle & THEMED_POPUP;
257 // Find a theme image.
258 NSColor* themeImageColor = nil;
261 if (active && incognito)
262 themeImageID = IDR_THEME_FRAME_INCOGNITO;
263 else if (active && !incognito)
264 themeImageID = IDR_THEME_FRAME;
265 else if (!active && incognito)
266 themeImageID = IDR_THEME_FRAME_INCOGNITO_INACTIVE;
268 themeImageID = IDR_THEME_FRAME_INACTIVE;
269 if (themeProvider->HasCustomImage(IDR_THEME_FRAME))
270 themeImageColor = themeProvider->GetNSImageColorNamed(themeImageID);
273 // If no theme image, use a gradient if incognito.
274 NSGradient* gradient = nil;
275 if (!themeImageColor && incognito)
276 gradient = themeProvider->GetNSGradient(
277 active ? ThemeProperties::GRADIENT_FRAME_INCOGNITO :
278 ThemeProperties::GRADIENT_FRAME_INCOGNITO_INACTIVE);
281 if (themeImageColor) {
282 // Default to replacing any existing pixels with the theme image, but if
283 // asked paint black first and blend the theme with black.
284 NSCompositingOperation operation = NSCompositeCopy;
285 if (forceBlackBackground) {
286 [[NSColor blackColor] set];
287 NSRectFill(dirtyRect);
288 operation = NSCompositeSourceOver;
291 NSPoint position = [[view window] themeImagePositionForAlignment:
292 THEME_IMAGE_ALIGN_WITH_FRAME];
293 [[NSGraphicsContext currentContext] cr_setPatternPhase:position
296 [themeImageColor set];
297 NSRectFillUsingOperation(dirtyRect, operation);
299 } else if (gradient) {
300 NSPoint startPoint = NSMakePoint(NSMinX(bounds), NSMaxY(bounds));
301 NSPoint endPoint = startPoint;
302 endPoint.y -= kBrowserFrameViewPaintHeight;
303 [gradient drawFromPoint:startPoint toPoint:endPoint options:0];
307 // Check to see if we have an overlay image.
308 NSImage* overlayImage = nil;
309 if (themeProvider->HasCustomImage(IDR_THEME_FRAME_OVERLAY) && !incognito &&
311 overlayImage = themeProvider->
312 GetNSImageNamed(active ? IDR_THEME_FRAME_OVERLAY :
313 IDR_THEME_FRAME_OVERLAY_INACTIVE);
317 // Anchor to top-left and don't scale.
318 NSPoint position = [[view window] themeImagePositionForAlignment:
319 THEME_IMAGE_ALIGN_WITH_FRAME];
320 position = [view convertPoint:position fromView:nil];
321 NSSize overlaySize = [overlayImage size];
322 NSRect imageFrame = NSMakeRect(0, 0, overlaySize.width, overlaySize.height);
323 [overlayImage drawAtPoint:NSMakePoint(position.x,
324 position.y - overlaySize.height)
326 operation:NSCompositeSourceOver
333 - (NSColor*)titleColor {
334 ui::ThemeProvider* themeProvider = [self themeProvider];
336 return [NSColor windowFrameTextColor];
338 ThemedWindowStyle windowStyle = [self themedWindowStyle];
339 BOOL incognito = windowStyle & THEMED_INCOGNITO;
342 return [NSColor whiteColor];
344 return [NSColor windowFrameTextColor];