Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / browser / zoom_bubble_controller.mm
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.
4
5 #include "chrome/browser/ui/cocoa/browser/zoom_bubble_controller.h"
6
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"
20
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
29                            action:(SEL)action;
30 - (NSTextField*)addZoomPercentTextField;
31 - (void)updateAutoCloseTimer;
32
33 // Get the WebContents instance and apply the indicated zoom.
34 - (void)zoomHelper:(content::PageZoom)alterPageZoom;
35 @end
36
37 // Button that highlights the background on mouse over.
38 @interface ZoomHoverButton : HoverButton
39 @end
40
41 namespace {
42
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;
47
48 // The height of the window.
49 const CGFloat kWindowHeight = 29.0;
50
51 // Width of the zoom in and zoom out buttons.
52 const CGFloat kZoomInOutButtonWidth = 44.0;
53
54 // Width of zoom label.
55 const CGFloat kZoomLabelWidth = 55.0;
56
57 // Horizontal margin for the reset zoom button.
58 const CGFloat kResetZoomMargin = 9.0;
59
60 // The font size text shown in the bubble.
61 const CGFloat kTextFontSize = 12.0;
62
63 // The font size of the zoom in and zoom out buttons.
64 const CGFloat kZoomInOutButtonFontSize = 16.0;
65
66 }  // namespace
67
68 namespace chrome {
69
70 void SetZoomBubbleAutoCloseDelayForTesting(NSTimeInterval time_interval) {
71   gAutoCloseDelay = time_interval;
72 }
73
74 }  // namespace chrome
75
76 @implementation ZoomBubbleController
77
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
84                                               defer:NO]);
85   if ((self = [super initWithWindow:window
86                        parentWindow:parentWindow
87                          anchoredAt:NSZeroPoint])) {
88     [window setCanBecomeKeyWindow:NO];
89     delegate_ = delegate;
90
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))];
97
98     [self performLayout];
99
100     trackingArea_.reset([[CrTrackingArea alloc]
101         initWithRect:NSZeroRect
102              options:NSTrackingMouseEnteredAndExited |
103                      NSTrackingActiveAlways |
104                      NSTrackingInVisibleRect
105                owner:self
106             userInfo:nil]);
107     [trackingArea_.get() clearOwnerWhenWindowWillClose:[self window]];
108     [[[self window] contentView] addTrackingArea:trackingArea_.get()];
109   }
110   return self;
111 }
112
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];
120
121   self.anchorPoint = anchorPoint;
122   [self showWindow:nil];
123
124   autoClose_ = autoClose;
125   [self updateAutoCloseTimer];
126 }
127
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
131   // tests.
132
133   content::WebContents* contents = delegate_->GetWebContents();
134   if (!contents)
135     return;
136
137   ZoomController* zoomController = ZoomController::FromWebContents(contents);
138   if (!zoomController)
139     return;
140
141   int percent = zoomController->GetZoomPercent();
142   NSString* string =
143       l10n_util::GetNSStringF(IDS_ZOOM_PERCENT, base::IntToString16(percent));
144   [zoomPercent_ setAttributedStringValue:
145       [self attributedStringWithString:string
146                               fontSize:kTextFontSize]];
147
148   [self updateAutoCloseTimer];
149 }
150
151 - (void)resetToDefault:(id)sender {
152   [self zoomHelper:content::PAGE_ZOOM_RESET];
153 }
154
155 - (void)zoomIn:(id)sender {
156   [self zoomHelper:content::PAGE_ZOOM_IN];
157 }
158
159 - (void)zoomOut:(id)sender {
160   [self zoomHelper:content::PAGE_ZOOM_OUT];
161 }
162
163 - (void)closeWithoutAnimation {
164   InfoBubbleWindow* window =
165       base::mac::ObjCCastStrict<InfoBubbleWindow>([self window]);
166   [window setAllowedAnimations:info_bubble::kAnimateNone];
167   [self close];
168 }
169
170 - (void)windowWillClose:(NSNotification*)notification {
171   delegate_->OnClose();
172   delegate_ = NULL;
173   [NSObject cancelPreviousPerformRequestsWithTarget:self
174                                            selector:@selector(autoCloseBubble)
175                                              object:nil];
176   [super windowWillClose:notification];
177 }
178
179 - (void)mouseEntered:(NSEvent*)theEvent {
180   isMouseInside_ = YES;
181   [self updateAutoCloseTimer];
182 }
183
184 - (void)mouseExited:(NSEvent*)theEvent {
185   isMouseInside_ = NO;
186   [self updateAutoCloseTimer];
187 }
188
189 // Private /////////////////////////////////////////////////////////////////////
190
191 - (void)performLayout {
192   // Zoom out button.
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];
198
199   // Zoom label.
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];
208
209   // Zoom in button.
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];
216
217   // Separator view.
218   rect.origin.x += NSWidth(rect);
219   rect.size.width = 1;
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];
228
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);
235   rect.size.width =
236       [[resetButton attributedTitle] size].width + kResetZoomMargin * 2.0;
237   [resetButton setFrame:rect];
238
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];
244 }
245
246 - (void)autoCloseBubble {
247   if (!autoClose_)
248     return;
249   [self close];
250 }
251
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 = @{
258       NSFontAttributeName:
259       [NSFont systemFontOfSize:fontSize],
260       NSForegroundColorAttributeName:
261       [NSColor colorWithCalibratedWhite:0.58 alpha:1.0],
262       NSParagraphStyleAttributeName:
263       paragraphStyle.get()
264   };
265   return [[[NSAttributedString alloc]
266       initWithString:string
267           attributes:attributes] autorelease];
268 }
269
270 - (NSButton*)addButtonWithTitleID:(int)titleID
271                          fontSize:(CGFloat)fontSize
272                            action:(SEL)action {
273   base::scoped_nsobject<NSButton> button(
274       [[ZoomHoverButton alloc] initWithFrame:NSZeroRect]);
275   NSString* title = l10n_util::GetNSStringWithFixup(titleID);
276   [button setAttributedTitle:[self attributedStringWithString:title
277                                                      fontSize:fontSize]];
278   [[button cell] setBordered:NO];
279   [button setTarget:self];
280   [button setAction:action];
281   [[[self window] contentView] addSubview:button];
282   return button.autorelease();
283 }
284
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();
293 }
294
295 - (void)updateAutoCloseTimer {
296   [NSObject cancelPreviousPerformRequestsWithTarget:self
297                                            selector:@selector(autoCloseBubble)
298                                              object:nil];
299   if (autoClose_ && !isMouseInside_) {
300     [self performSelector:@selector(autoCloseBubble)
301                withObject:nil
302                afterDelay:gAutoCloseDelay];
303   }
304 }
305
306 - (void)zoomHelper:(content::PageZoom)alterPageZoom {
307   content::WebContents* webContents = delegate_->GetWebContents();
308
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);
314 }
315
316 @end
317
318 @implementation ZoomHoverButton
319
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);
328
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();
339   }
340
341   [[self cell] drawTitle:title
342                withFrame:bounds
343                   inView:self];
344 }
345
346 @end