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_view.h"
11 #import "chrome/browser/ui/cocoa/info_bubble_window.h"
12 #include "chrome/browser/ui/zoom/zoom_controller.h"
13 #include "chrome/grit/generated_resources.h"
14 #include "content/public/common/page_zoom.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;
33 // Get the WebContents instance and apply the indicated zoom.
34 - (void)zoomHelper:(content::PageZoom)alterPageZoom;
37 // Button that highlights the background on mouse over.
38 @interface ZoomHoverButton : HoverButton
43 // The amount of time to wait before the bubble automatically closes.
44 // Should keep in sync with kBubbleCloseDelay in
45 // src/chrome/browser/ui/views/location_bar/zoom_bubble_view.cc.
46 NSTimeInterval gAutoCloseDelay = 1.5;
48 // The height of the window.
49 const CGFloat kWindowHeight = 29.0;
51 // Width of the zoom in and zoom out buttons.
52 const CGFloat kZoomInOutButtonWidth = 44.0;
54 // Width of zoom label.
55 const CGFloat kZoomLabelWidth = 55.0;
57 // Horizontal margin for the reset zoom button.
58 const CGFloat kResetZoomMargin = 9.0;
60 // The font size text shown in the bubble.
61 const CGFloat kTextFontSize = 12.0;
63 // The font size of the zoom in and zoom out buttons.
64 const CGFloat kZoomInOutButtonFontSize = 16.0;
70 void SetZoomBubbleAutoCloseDelayForTesting(NSTimeInterval time_interval) {
71 gAutoCloseDelay = time_interval;
76 @implementation ZoomBubbleController
78 - (id)initWithParentWindow:(NSWindow*)parentWindow
79 delegate:(ZoomBubbleControllerDelegate*)delegate {
80 base::scoped_nsobject<InfoBubbleWindow> window(
81 [[InfoBubbleWindow alloc] initWithContentRect:NSMakeRect(0, 0, 200, 100)
82 styleMask:NSBorderlessWindowMask
83 backing:NSBackingStoreBuffered
85 if ((self = [super initWithWindow:window
86 parentWindow:parentWindow
87 anchoredAt:NSZeroPoint])) {
88 [window setCanBecomeKeyWindow:NO];
91 ui::NativeTheme* nativeTheme = ui::NativeTheme::instance();
92 [[self bubble] setAlignment:info_bubble::kAlignRightEdgeToAnchorEdge];
93 [[self bubble] setArrowLocation:info_bubble::kNoArrow];
94 [[self bubble] setBackgroundColor:
95 gfx::SkColorToCalibratedNSColor(nativeTheme->GetSystemColor(
96 ui::NativeTheme::kColorId_DialogBackground))];
100 trackingArea_.reset([[CrTrackingArea alloc]
101 initWithRect:NSZeroRect
102 options:NSTrackingMouseEnteredAndExited |
103 NSTrackingActiveAlways |
104 NSTrackingInVisibleRect
107 [trackingArea_.get() clearOwnerWhenWindowWillClose:[self window]];
108 [[[self window] contentView] addTrackingArea:trackingArea_.get()];
113 - (void)showAnchoredAt:(NSPoint)anchorPoint autoClose:(BOOL)autoClose {
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 {
129 // TODO(shess): It may be appropriate to close the window if
130 // |contents| or |zoomController| are NULL. But they can be NULL in
133 content::WebContents* contents = delegate_->GetWebContents();
137 ZoomController* zoomController = ZoomController::FromWebContents(contents);
141 int percent = zoomController->GetZoomPercent();
143 l10n_util::GetNSStringF(IDS_ZOOM_PERCENT, base::IntToString16(percent));
144 [zoomPercent_ setAttributedStringValue:
145 [self attributedStringWithString:string
146 fontSize:kTextFontSize]];
148 [self updateAutoCloseTimer];
151 - (void)resetToDefault:(id)sender {
152 [self zoomHelper:content::PAGE_ZOOM_RESET];
155 - (void)zoomIn:(id)sender {
156 [self zoomHelper:content::PAGE_ZOOM_IN];
159 - (void)zoomOut:(id)sender {
160 [self zoomHelper:content::PAGE_ZOOM_OUT];
163 - (void)closeWithoutAnimation {
164 InfoBubbleWindow* window =
165 base::mac::ObjCCastStrict<InfoBubbleWindow>([self window]);
166 [window setAllowedAnimations:info_bubble::kAnimateNone];
170 - (void)windowWillClose:(NSNotification*)notification {
171 delegate_->OnClose();
173 [NSObject cancelPreviousPerformRequestsWithTarget:self
174 selector:@selector(autoCloseBubble)
176 [super windowWillClose:notification];
179 - (void)mouseEntered:(NSEvent*)theEvent {
180 isMouseInside_ = YES;
181 [self updateAutoCloseTimer];
184 - (void)mouseExited:(NSEvent*)theEvent {
186 [self updateAutoCloseTimer];
189 // Private /////////////////////////////////////////////////////////////////////
191 - (void)performLayout {
193 NSButton* zoomOutButton = [self addButtonWithTitleID:IDS_ZOOM_MINUS2
194 fontSize:kZoomInOutButtonFontSize
195 action:@selector(zoomOut:)];
196 NSRect rect = NSMakeRect(0, 0, kZoomInOutButtonWidth, kWindowHeight);
197 [zoomOutButton setFrame:rect];
200 zoomPercent_.reset([[self addZoomPercentTextField] retain]);
201 rect.origin.x += NSWidth(rect);
202 rect.size.width = kZoomLabelWidth;
203 [zoomPercent_ sizeToFit];
204 NSRect zoomRect = rect;
205 zoomRect.size.height = NSHeight([zoomPercent_ frame]);
206 zoomRect.origin.y = roundf((NSHeight(rect) - NSHeight(zoomRect)) / 2.0);
207 [zoomPercent_ setFrame:zoomRect];
210 NSButton* zoomInButton = [self addButtonWithTitleID:IDS_ZOOM_PLUS2
211 fontSize:kZoomInOutButtonFontSize
212 action:@selector(zoomIn:)];
213 rect.origin.x += NSWidth(rect);
214 rect.size.width = kZoomInOutButtonWidth;
215 [zoomInButton setFrame:rect];
218 rect.origin.x += NSWidth(rect);
220 base::scoped_nsobject<NSBox> separatorView(
221 [[NSBox alloc] initWithFrame:rect]);
222 [separatorView setBoxType:NSBoxCustom];
223 ui::NativeTheme* nativeTheme = ui::NativeTheme::instance();
224 [separatorView setBorderColor:
225 gfx::SkColorToCalibratedNSColor(nativeTheme->GetSystemColor(
226 ui::NativeTheme::kColorId_MenuSeparatorColor))];
227 [[[self window] contentView] addSubview:separatorView];
229 // Reset zoom button.
230 NSButton* resetButton =
231 [self addButtonWithTitleID:IDS_ZOOM_SET_DEFAULT_SHORT
232 fontSize:kTextFontSize
233 action:@selector(resetToDefault:)];
234 rect.origin.x += NSWidth(rect);
236 [[resetButton attributedTitle] size].width + kResetZoomMargin * 2.0;
237 [resetButton setFrame:rect];
239 // Update window frame.
240 NSRect windowFrame = [[self window] frame];
241 windowFrame.size.height = NSHeight(rect);
242 windowFrame.size.width = NSMaxX(rect);
243 [[self window] setFrame:windowFrame display:YES];
246 - (void)autoCloseBubble {
252 - (NSAttributedString*)attributedStringWithString:(NSString*)string
253 fontSize:(CGFloat)fontSize {
254 base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
255 [[NSMutableParagraphStyle alloc] init]);
256 [paragraphStyle setAlignment:NSCenterTextAlignment];
257 NSDictionary* attributes = @{
259 [NSFont systemFontOfSize:fontSize],
260 NSForegroundColorAttributeName:
261 [NSColor colorWithCalibratedWhite:0.58 alpha:1.0],
262 NSParagraphStyleAttributeName:
265 return [[[NSAttributedString alloc]
266 initWithString:string
267 attributes:attributes] autorelease];
270 - (NSButton*)addButtonWithTitleID:(int)titleID
271 fontSize:(CGFloat)fontSize
273 base::scoped_nsobject<NSButton> button(
274 [[ZoomHoverButton alloc] initWithFrame:NSZeroRect]);
275 NSString* title = l10n_util::GetNSStringWithFixup(titleID);
276 [button setAttributedTitle:[self attributedStringWithString:title
278 [[button cell] setBordered:NO];
279 [button setTarget:self];
280 [button setAction:action];
281 [[[self window] contentView] addSubview:button];
282 return button.autorelease();
285 - (NSTextField*)addZoomPercentTextField {
286 base::scoped_nsobject<NSTextField> textField(
287 [[NSTextField alloc] initWithFrame:NSZeroRect]);
288 [textField setEditable:NO];
289 [textField setBordered:NO];
290 [textField setDrawsBackground:NO];
291 [[[self window] contentView] addSubview:textField];
292 return textField.autorelease();
295 - (void)updateAutoCloseTimer {
296 [NSObject cancelPreviousPerformRequestsWithTarget:self
297 selector:@selector(autoCloseBubble)
299 if (autoClose_ && !isMouseInside_) {
300 [self performSelector:@selector(autoCloseBubble)
302 afterDelay:gAutoCloseDelay];
306 - (void)zoomHelper:(content::PageZoom)alterPageZoom {
307 content::WebContents* webContents = delegate_->GetWebContents();
309 // TODO(shess): Zoom() immediately dereferences |webContents|, and
310 // there haven't been associated crashes in the wild, so it seems
311 // fine in practice. It might make sense to close the bubble in
312 // that case, though.
313 chrome_page_zoom::Zoom(webContents, alterPageZoom);
318 @implementation ZoomHoverButton
320 - (void)drawRect:(NSRect)rect {
321 NSRect bounds = [self bounds];
322 NSAttributedString* title = [self attributedTitle];
323 if ([self hoverState] != kHoverStateNone) {
324 ui::NativeTheme* nativeTheme = ui::NativeTheme::instance();
325 [gfx::SkColorToCalibratedNSColor(nativeTheme->GetSystemColor(
326 ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor)) set];
327 NSRectFillUsingOperation(bounds, NSCompositeSourceOver);
329 // Change the title color.
330 base::scoped_nsobject<NSMutableAttributedString> selectedTitle(
331 [[NSMutableAttributedString alloc] initWithAttributedString:title]);
332 NSColor* selectedTitleColor =
333 gfx::SkColorToCalibratedNSColor(nativeTheme->GetSystemColor(
334 ui::NativeTheme::kColorId_SelectedMenuItemForegroundColor));
335 [selectedTitle addAttribute:NSForegroundColorAttributeName
336 value:selectedTitleColor
337 range:NSMakeRange(0, [title length])];
338 title = selectedTitle.autorelease();
341 [[self cell] drawTitle:title