1 // Copyright (c) 2013 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 #include "chrome/browser/ui/cocoa/browser/zoom_bubble_controller.h"
7 #include "base/mac/foundation_util.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "chrome/browser/chrome_page_zoom.h"
10 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
11 #import "chrome/browser/ui/cocoa/info_bubble_view.h"
12 #include "chrome/browser/ui/zoom/zoom_controller.h"
13 #include "content/public/common/page_zoom.h"
14 #include "grit/generated_resources.h"
15 #include "skia/ext/skia_utils_mac.h"
16 #import "ui/base/cocoa/hover_button.h"
17 #import "ui/base/cocoa/window_size_constants.h"
18 #include "ui/base/l10n/l10n_util.h"
19 #include "ui/native_theme/native_theme.h"
21 @interface ZoomBubbleController (Private)
22 - (void)performLayout;
23 - (void)autoCloseBubble;
24 - (NSAttributedString*)attributedStringWithString:(NSString*)string
25 fontSize:(CGFloat)fontSize;
26 // Adds a new zoom button to the bubble.
27 - (NSButton*)addButtonWithTitleID:(int)titleID
28 fontSize:(CGFloat)fontSize
30 - (NSTextField*)addZoomPercentTextField;
31 - (void)updateAutoCloseTimer;
34 // Button that highlights the background on mouse over.
35 @interface ZoomHoverButton : HoverButton
40 // The amount of time to wait before the bubble automatically closes.
41 // Should keep in sync with kBubbleCloseDelay in
42 // src/chrome/browser/ui/views/location_bar/zoom_bubble_view.cc.
43 NSTimeInterval gAutoCloseDelay = 1.5;
45 // The height of the window.
46 const CGFloat kWindowHeight = 29.0;
48 // Width of the zoom in and zoom out buttons.
49 const CGFloat kZoomInOutButtonWidth = 44.0;
51 // Width of zoom label.
52 const CGFloat kZoomLabelWidth = 55.0;
54 // Horizontal margin for the reset zoom button.
55 const CGFloat kResetZoomMargin = 9.0;
57 // The font size text shown in the bubble.
58 const CGFloat kTextFontSize = 12.0;
60 // The font size of the zoom in and zoom out buttons.
61 const CGFloat kZoomInOutButtonFontSize = 16.0;
67 void SetZoomBubbleAutoCloseDelayForTesting(NSTimeInterval time_interval) {
68 gAutoCloseDelay = time_interval;
73 @implementation ZoomBubbleController
75 - (id)initWithParentWindow:(NSWindow*)parentWindow
76 closeObserver:(void(^)(ZoomBubbleController*))closeObserver {
77 base::scoped_nsobject<InfoBubbleWindow> window(
78 [[InfoBubbleWindow alloc] initWithContentRect:NSMakeRect(0, 0, 200, 100)
79 styleMask:NSBorderlessWindowMask
80 backing:NSBackingStoreBuffered
82 if ((self = [super initWithWindow:window
83 parentWindow:parentWindow
84 anchoredAt:NSZeroPoint])) {
85 [window setCanBecomeKeyWindow:NO];
86 closeObserver_.reset(Block_copy(closeObserver));
88 ui::NativeTheme* nativeTheme = ui::NativeTheme::instance();
89 [[self bubble] setAlignment:info_bubble::kAlignRightEdgeToAnchorEdge];
90 [[self bubble] setArrowLocation:info_bubble::kNoArrow];
91 [[self bubble] setBackgroundColor:
92 gfx::SkColorToCalibratedNSColor(nativeTheme->GetSystemColor(
93 ui::NativeTheme::kColorId_DialogBackground))];
97 trackingArea_.reset([[CrTrackingArea alloc]
98 initWithRect:NSZeroRect
99 options:NSTrackingMouseEnteredAndExited |
100 NSTrackingActiveAlways |
101 NSTrackingInVisibleRect
104 [trackingArea_.get() clearOwnerWhenWindowWillClose:[self window]];
105 [[[self window] contentView] addTrackingArea:trackingArea_.get()];
110 - (void)showForWebContents:(content::WebContents*)contents
111 anchoredAt:(NSPoint)anchorPoint
112 autoClose:(BOOL)autoClose {
113 contents_ = contents;
114 [self onZoomChanged];
115 InfoBubbleWindow* window =
116 base::mac::ObjCCastStrict<InfoBubbleWindow>([self window]);
117 [window setAllowedAnimations:autoClose
118 ? info_bubble::kAnimateOrderIn | info_bubble::kAnimateOrderOut
119 : info_bubble::kAnimateNone];
121 self.anchorPoint = anchorPoint;
122 [self showWindow:nil];
124 autoClose_ = autoClose;
125 [self updateAutoCloseTimer];
128 - (void)onZoomChanged {
130 return; // NULL in tests.
132 ZoomController* zoomController = ZoomController::FromWebContents(contents_);
136 int percent = zoomController->zoom_percent();
138 l10n_util::GetNSStringF(IDS_ZOOM_PERCENT, base::IntToString16(percent));
139 [zoomPercent_ setAttributedStringValue:
140 [self attributedStringWithString:string
141 fontSize:kTextFontSize]];
143 [self updateAutoCloseTimer];
146 - (void)resetToDefault:(id)sender {
147 chrome_page_zoom::Zoom(contents_, content::PAGE_ZOOM_RESET);
150 - (void)zoomIn:(id)sender {
151 chrome_page_zoom::Zoom(contents_, content::PAGE_ZOOM_IN);
154 - (void)zoomOut:(id)sender {
155 chrome_page_zoom::Zoom(contents_, content::PAGE_ZOOM_OUT);
158 - (void)closeWithoutAnimation {
159 InfoBubbleWindow* window =
160 base::mac::ObjCCastStrict<InfoBubbleWindow>([self window]);
161 [window setAllowedAnimations:info_bubble::kAnimateNone];
165 - (void)windowWillClose:(NSNotification*)notification {
167 closeObserver_.get()(self);
168 [NSObject cancelPreviousPerformRequestsWithTarget:self
169 selector:@selector(autoCloseBubble)
171 [super windowWillClose:notification];
174 - (void)mouseEntered:(NSEvent*)theEvent {
175 isMouseInside_ = YES;
176 [self updateAutoCloseTimer];
179 - (void)mouseExited:(NSEvent*)theEvent {
181 [self updateAutoCloseTimer];
184 // Private /////////////////////////////////////////////////////////////////////
186 - (void)performLayout {
188 NSButton* zoomOutButton = [self addButtonWithTitleID:IDS_ZOOM_MINUS2
189 fontSize:kZoomInOutButtonFontSize
190 action:@selector(zoomOut:)];
191 NSRect rect = NSMakeRect(0, 0, kZoomInOutButtonWidth, kWindowHeight);
192 [zoomOutButton setFrame:rect];
195 zoomPercent_.reset([[self addZoomPercentTextField] retain]);
196 rect.origin.x += NSWidth(rect);
197 rect.size.width = kZoomLabelWidth;
198 [zoomPercent_ sizeToFit];
199 NSRect zoomRect = rect;
200 zoomRect.size.height = NSHeight([zoomPercent_ frame]);
201 zoomRect.origin.y = roundf((NSHeight(rect) - NSHeight(zoomRect)) / 2.0);
202 [zoomPercent_ setFrame:zoomRect];
205 NSButton* zoomInButton = [self addButtonWithTitleID:IDS_ZOOM_PLUS2
206 fontSize:kZoomInOutButtonFontSize
207 action:@selector(zoomIn:)];
208 rect.origin.x += NSWidth(rect);
209 rect.size.width = kZoomInOutButtonWidth;
210 [zoomInButton setFrame:rect];
213 rect.origin.x += NSWidth(rect);
215 base::scoped_nsobject<NSBox> separatorView(
216 [[NSBox alloc] initWithFrame:rect]);
217 [separatorView setBoxType:NSBoxCustom];
218 ui::NativeTheme* nativeTheme = ui::NativeTheme::instance();
219 [separatorView setBorderColor:
220 gfx::SkColorToCalibratedNSColor(nativeTheme->GetSystemColor(
221 ui::NativeTheme::kColorId_MenuSeparatorColor))];
222 [[[self window] contentView] addSubview:separatorView];
224 // Reset zoom button.
225 NSButton* resetButton =
226 [self addButtonWithTitleID:IDS_ZOOM_SET_DEFAULT_SHORT
227 fontSize:kTextFontSize
228 action:@selector(resetToDefault:)];
229 rect.origin.x += NSWidth(rect);
231 [[resetButton attributedTitle] size].width + kResetZoomMargin * 2.0;
232 [resetButton setFrame:rect];
234 // Update window frame.
235 NSRect windowFrame = [[self window] frame];
236 windowFrame.size.height = NSHeight(rect);
237 windowFrame.size.width = NSMaxX(rect);
238 [[self window] setFrame:windowFrame display:YES];
241 - (void)autoCloseBubble {
247 - (NSAttributedString*)attributedStringWithString:(NSString*)string
248 fontSize:(CGFloat)fontSize {
249 base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
250 [[NSMutableParagraphStyle alloc] init]);
251 [paragraphStyle setAlignment:NSCenterTextAlignment];
252 NSDictionary* attributes = @{
254 [NSFont systemFontOfSize:fontSize],
255 NSForegroundColorAttributeName:
256 [NSColor colorWithCalibratedWhite:0.58 alpha:1.0],
257 NSParagraphStyleAttributeName:
260 return [[[NSAttributedString alloc]
261 initWithString:string
262 attributes:attributes] autorelease];
265 - (NSButton*)addButtonWithTitleID:(int)titleID
266 fontSize:(CGFloat)fontSize
268 base::scoped_nsobject<NSButton> button(
269 [[ZoomHoverButton alloc] initWithFrame:NSZeroRect]);
270 NSString* title = l10n_util::GetNSStringWithFixup(titleID);
271 [button setAttributedTitle:[self attributedStringWithString:title
273 [[button cell] setBordered:NO];
274 [button setTarget:self];
275 [button setAction:action];
276 [[[self window] contentView] addSubview:button];
277 return button.autorelease();
280 - (NSTextField*)addZoomPercentTextField {
281 base::scoped_nsobject<NSTextField> textField(
282 [[NSTextField alloc] initWithFrame:NSZeroRect]);
283 [textField setEditable:NO];
284 [textField setBordered:NO];
285 [textField setDrawsBackground:NO];
286 [[[self window] contentView] addSubview:textField];
287 return textField.autorelease();
290 - (void)updateAutoCloseTimer {
291 [NSObject cancelPreviousPerformRequestsWithTarget:self
292 selector:@selector(autoCloseBubble)
294 if (autoClose_ && !isMouseInside_) {
295 [self performSelector:@selector(autoCloseBubble)
297 afterDelay:gAutoCloseDelay];
303 @implementation ZoomHoverButton
305 - (void)drawRect:(NSRect)rect {
306 NSRect bounds = [self bounds];
307 NSAttributedString* title = [self attributedTitle];
308 if ([self hoverState] != kHoverStateNone) {
309 ui::NativeTheme* nativeTheme = ui::NativeTheme::instance();
310 [gfx::SkColorToCalibratedNSColor(nativeTheme->GetSystemColor(
311 ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor)) set];
312 NSRectFillUsingOperation(bounds, NSCompositeSourceOver);
314 // Change the title color.
315 base::scoped_nsobject<NSMutableAttributedString> selectedTitle(
316 [[NSMutableAttributedString alloc] initWithAttributedString:title]);
317 NSColor* selectedTitleColor =
318 gfx::SkColorToCalibratedNSColor(nativeTheme->GetSystemColor(
319 ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor));
320 [selectedTitle addAttribute:NSForegroundColorAttributeName
321 value:selectedTitleColor
322 range:NSMakeRange(0, [title length])];
323 title = selectedTitle.autorelease();
326 [[self cell] drawTitle:title