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 - (void)setStyleMask:(NSUInteger)styleMask {
49 if (frameAndStyleMaskLock_)
51 [super setStyleMask:styleMask];
54 - (void)setFrame:(NSRect)windowFrame
55 display:(BOOL)displayViews
56 animate:(BOOL)performAnimation {
57 if (frameAndStyleMaskLock_)
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
71 backing:NSBackingStoreBuffered
73 wantsViewsOverTitlebar:hasTabStrip])) {
74 // The 10.6 fullscreen code copies the title to a different window, which
75 // will assert if it's nil.
78 // The following two calls fix http://crbug.com/25684 by preventing the
79 // window from recalculating the border thickness as the window is
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
115 frameAndStyleMaskLock_ = NO;
122 [[NSNotificationCenter defaultCenter] removeObserver:self];
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;
152 NSMakePoint(xOffset, (NSHeight([self frame]) -
153 NSHeight(buttonFrame) - yOffset));
156 case NSWindowZoomButton:
157 buttonFrame.origin.x += NSWidth([miniaturizeButton_ frame]);
158 buttonFrame.origin.x += windowButtonsInterButtonSpacing_;
160 case NSWindowMiniaturizeButton:
161 buttonFrame.origin.x += NSWidth([closeButton_ frame]);
162 buttonFrame.origin.x += windowButtonsInterButtonSpacing_;
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_ };
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];
187 value = [super accessibilityHitTest:point];
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])
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 {
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);
259 + (BOOL)drawWindowThemeInDirtyRect:(NSRect)dirtyRect
260 forView:(NSView*)view
261 bounds:(NSRect)bounds
262 forceBlackBackground:(BOOL)forceBlackBackground {
263 ui::ThemeProvider* themeProvider = [[view window] themeProvider];
267 ThemedWindowStyle windowStyle = [[view window] themedWindowStyle];
269 // Devtools windows don't get themed.
270 if (windowStyle & THEMED_DEVTOOLS)
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;
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;
288 themeImageID = IDR_THEME_FRAME_INACTIVE;
289 if (themeProvider->HasCustomImage(IDR_THEME_FRAME))
290 themeImageColor = themeProvider->GetNSImageColorNamed(themeImageID);
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);
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;
311 NSPoint position = [[view window] themeImagePositionForAlignment:
312 THEME_IMAGE_ALIGN_WITH_FRAME];
313 [[NSGraphicsContext currentContext] cr_setPatternPhase:position
316 [themeImageColor set];
317 NSRectFillUsingOperation(dirtyRect, operation);
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];
327 // Check to see if we have an overlay image.
328 NSImage* overlayImage = nil;
329 if (themeProvider->HasCustomImage(IDR_THEME_FRAME_OVERLAY) && !incognito &&
331 overlayImage = themeProvider->
332 GetNSImageNamed(active ? IDR_THEME_FRAME_OVERLAY :
333 IDR_THEME_FRAME_OVERLAY_INACTIVE);
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)
346 operation:NSCompositeSourceOver
353 - (NSColor*)titleColor {
354 ui::ThemeProvider* themeProvider = [self themeProvider];
356 return [NSColor windowFrameTextColor];
358 ThemedWindowStyle windowStyle = [self themedWindowStyle];
359 BOOL incognito = windowStyle & THEMED_INCOGNITO;
362 return [NSColor whiteColor];
364 return [NSColor windowFrameTextColor];