blob: d1378da466a5a5aeb19f6667a7d635739afbc67f [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/cocoa/browser/zoom_bubble_controller.h"
#include "base/mac/foundation_util.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/chrome_page_zoom.h"
#import "chrome/browser/ui/cocoa/info_bubble_window.h"
#import "chrome/browser/ui/cocoa/info_bubble_view.h"
#include "chrome/browser/ui/zoom/zoom_controller.h"
#include "content/public/common/page_zoom.h"
#include "grit/generated_resources.h"
#include "skia/ext/skia_utils_mac.h"
#import "ui/base/cocoa/hover_button.h"
#import "ui/base/cocoa/window_size_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/native_theme/native_theme.h"
@interface ZoomBubbleController (Private)
- (void)performLayout;
- (void)autoCloseBubble;
- (NSAttributedString*)attributedStringWithString:(NSString*)string
fontSize:(CGFloat)fontSize;
// Adds a new zoom button to the bubble.
- (NSButton*)addButtonWithTitleID:(int)titleID
fontSize:(CGFloat)fontSize
action:(SEL)action;
- (NSTextField*)addZoomPercentTextField;
- (void)updateAutoCloseTimer;
@end
// Button that highlights the background on mouse over.
@interface ZoomHoverButton : HoverButton
@end
namespace {
// The amount of time to wait before the bubble automatically closes.
// Should keep in sync with kBubbleCloseDelay in
// src/chrome/browser/ui/views/location_bar/zoom_bubble_view.cc.
NSTimeInterval gAutoCloseDelay = 1.5;
// The height of the window.
const CGFloat kWindowHeight = 29.0;
// Width of the zoom in and zoom out buttons.
const CGFloat kZoomInOutButtonWidth = 44.0;
// Width of zoom label.
const CGFloat kZoomLabelWidth = 55.0;
// Horizontal margin for the reset zoom button.
const CGFloat kResetZoomMargin = 9.0;
// The font size text shown in the bubble.
const CGFloat kTextFontSize = 12.0;
// The font size of the zoom in and zoom out buttons.
const CGFloat kZoomInOutButtonFontSize = 16.0;
} // namespace
namespace chrome {
void SetZoomBubbleAutoCloseDelayForTesting(NSTimeInterval time_interval) {
gAutoCloseDelay = time_interval;
}
} // namespace chrome
@implementation ZoomBubbleController
- (id)initWithParentWindow:(NSWindow*)parentWindow
closeObserver:(void(^)(ZoomBubbleController*))closeObserver {
base::scoped_nsobject<InfoBubbleWindow> window(
[[InfoBubbleWindow alloc] initWithContentRect:NSMakeRect(0, 0, 200, 100)
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO]);
if ((self = [super initWithWindow:window
parentWindow:parentWindow
anchoredAt:NSZeroPoint])) {
closeObserver_.reset(Block_copy(closeObserver));
ui::NativeTheme* nativeTheme = ui::NativeTheme::instance();
[[self bubble] setAlignment:info_bubble::kAlignRightEdgeToAnchorEdge];
[[self bubble] setArrowLocation:info_bubble::kNoArrow];
[[self bubble] setBackgroundColor:
gfx::SkColorToCalibratedNSColor(nativeTheme->GetSystemColor(
ui::NativeTheme::kColorId_DialogBackground))];
[self performLayout];
trackingArea_.reset([[CrTrackingArea alloc]
initWithRect:NSZeroRect
options:NSTrackingMouseEnteredAndExited |
NSTrackingActiveInKeyWindow |
NSTrackingInVisibleRect
owner:self
userInfo:nil]);
[trackingArea_.get() clearOwnerWhenWindowWillClose:[self window]];
[[[self window] contentView] addTrackingArea:trackingArea_.get()];
}
return self;
}
- (void)showForWebContents:(content::WebContents*)contents
anchoredAt:(NSPoint)anchorPoint
autoClose:(BOOL)autoClose {
contents_ = contents;
[self onZoomChanged];
InfoBubbleWindow* window =
base::mac::ObjCCastStrict<InfoBubbleWindow>([self window]);
[window setAllowedAnimations:autoClose
? info_bubble::kAnimateOrderIn | info_bubble::kAnimateOrderOut
: info_bubble::kAnimateNone];
self.anchorPoint = anchorPoint;
[self showWindow:nil];
autoClose_ = autoClose;
[self updateAutoCloseTimer];
}
- (void)onZoomChanged {
if (!contents_)
return; // NULL in tests.
ZoomController* zoomController = ZoomController::FromWebContents(contents_);
if (!zoomController)
return;
int percent = zoomController->zoom_percent();
NSString* string =
l10n_util::GetNSStringF(IDS_ZOOM_PERCENT, base::IntToString16(percent));
[zoomPercent_ setAttributedStringValue:
[self attributedStringWithString:string
fontSize:kTextFontSize]];
[self updateAutoCloseTimer];
}
- (void)resetToDefault:(id)sender {
chrome_page_zoom::Zoom(contents_, content::PAGE_ZOOM_RESET);
}
- (void)zoomIn:(id)sender {
chrome_page_zoom::Zoom(contents_, content::PAGE_ZOOM_IN);
}
- (void)zoomOut:(id)sender {
chrome_page_zoom::Zoom(contents_, content::PAGE_ZOOM_OUT);
}
- (void)closeWithoutAnimation {
InfoBubbleWindow* window =
base::mac::ObjCCastStrict<InfoBubbleWindow>([self window]);
[window setAllowedAnimations:info_bubble::kAnimateNone];
[self close];
}
- (void)windowWillClose:(NSNotification*)notification {
contents_ = NULL;
closeObserver_.get()(self);
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(autoCloseBubble)
object:nil];
[super windowWillClose:notification];
}
- (void)mouseEntered:(NSEvent*)theEvent {
isMouseInside_ = YES;
[self updateAutoCloseTimer];
}
- (void)mouseExited:(NSEvent*)theEvent {
isMouseInside_ = NO;
[self updateAutoCloseTimer];
}
// Private /////////////////////////////////////////////////////////////////////
- (void)performLayout {
// Zoom out button.
NSButton* zoomOutButton = [self addButtonWithTitleID:IDS_ZOOM_MINUS2
fontSize:kZoomInOutButtonFontSize
action:@selector(zoomOut:)];
NSRect rect = NSMakeRect(0, 0, kZoomInOutButtonWidth, kWindowHeight);
[zoomOutButton setFrame:rect];
// Zoom label.
zoomPercent_.reset([[self addZoomPercentTextField] retain]);
rect.origin.x += NSWidth(rect);
rect.size.width = kZoomLabelWidth;
[zoomPercent_ sizeToFit];
NSRect zoomRect = rect;
zoomRect.size.height = NSHeight([zoomPercent_ frame]);
zoomRect.origin.y = roundf((NSHeight(rect) - NSHeight(zoomRect)) / 2.0);
[zoomPercent_ setFrame:zoomRect];
// Zoom in button.
NSButton* zoomInButton = [self addButtonWithTitleID:IDS_ZOOM_PLUS2
fontSize:kZoomInOutButtonFontSize
action:@selector(zoomIn:)];
rect.origin.x += NSWidth(rect);
rect.size.width = kZoomInOutButtonWidth;
[zoomInButton setFrame:rect];
// Separator view.
rect.origin.x += NSWidth(rect);
rect.size.width = 1;
base::scoped_nsobject<NSBox> separatorView(
[[NSBox alloc] initWithFrame:rect]);
[separatorView setBoxType:NSBoxCustom];
ui::NativeTheme* nativeTheme = ui::NativeTheme::instance();
[separatorView setBorderColor:
gfx::SkColorToCalibratedNSColor(nativeTheme->GetSystemColor(
ui::NativeTheme::kColorId_MenuSeparatorColor))];
[[[self window] contentView] addSubview:separatorView];
// Reset zoom button.
NSButton* resetButton =
[self addButtonWithTitleID:IDS_ZOOM_SET_DEFAULT_SHORT
fontSize:kTextFontSize
action:@selector(resetToDefault:)];
rect.origin.x += NSWidth(rect);
rect.size.width =
[[resetButton attributedTitle] size].width + kResetZoomMargin * 2.0;
[resetButton setFrame:rect];
// Update window frame.
NSRect windowFrame = [[self window] frame];
windowFrame.size.height = NSHeight(rect);
windowFrame.size.width = NSMaxX(rect);
[[self window] setFrame:windowFrame display:YES];
}
- (void)autoCloseBubble {
if (!autoClose_)
return;
[self close];
}
- (NSAttributedString*)attributedStringWithString:(NSString*)string
fontSize:(CGFloat)fontSize {
base::scoped_nsobject<NSMutableParagraphStyle> paragraphStyle(
[[NSMutableParagraphStyle alloc] init]);
[paragraphStyle setAlignment:NSCenterTextAlignment];
NSDictionary* attributes = @{
NSFontAttributeName:
[NSFont systemFontOfSize:fontSize],
NSForegroundColorAttributeName:
[NSColor colorWithCalibratedWhite:0.58 alpha:1.0],
NSParagraphStyleAttributeName:
paragraphStyle.get()
};
return [[[NSAttributedString alloc]
initWithString:string
attributes:attributes] autorelease];
}
- (NSButton*)addButtonWithTitleID:(int)titleID
fontSize:(CGFloat)fontSize
action:(SEL)action {
base::scoped_nsobject<NSButton> button(
[[ZoomHoverButton alloc] initWithFrame:NSZeroRect]);
NSString* title = l10n_util::GetNSStringWithFixup(titleID);
[button setAttributedTitle:[self attributedStringWithString:title
fontSize:fontSize]];
[[button cell] setBordered:NO];
[button setTarget:self];
[button setAction:action];
[[[self window] contentView] addSubview:button];
return button.autorelease();
}
- (NSTextField*)addZoomPercentTextField {
base::scoped_nsobject<NSTextField> textField(
[[NSTextField alloc] initWithFrame:NSZeroRect]);
[textField setEditable:NO];
[textField setBordered:NO];
[textField setDrawsBackground:NO];
[[[self window] contentView] addSubview:textField];
return textField.autorelease();
}
- (void)updateAutoCloseTimer {
[NSObject cancelPreviousPerformRequestsWithTarget:self
selector:@selector(autoCloseBubble)
object:nil];
if (autoClose_ && !isMouseInside_) {
[self performSelector:@selector(autoCloseBubble)
withObject:nil
afterDelay:gAutoCloseDelay];
}
}
@end
@implementation ZoomHoverButton
- (void)drawRect:(NSRect)rect {
NSRect bounds = [self bounds];
if ([self hoverState] != kHoverStateNone) {
ui::NativeTheme* nativeTheme = ui::NativeTheme::instance();
[gfx::SkColorToCalibratedNSColor(nativeTheme->GetSystemColor(
ui::NativeTheme::kColorId_FocusedMenuItemBackgroundColor)) set];
NSRectFillUsingOperation(bounds, NSCompositeSourceOver);
}
[[self cell] drawTitle:[self attributedTitle]
withFrame:bounds
inView:self];
}
@end