Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / extensions / browser_action_button.mm
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.
4
5 #import "chrome/browser/ui/cocoa/extensions/browser_action_button.h"
6
7 #include <algorithm>
8 #include <cmath>
9
10 #include "base/logging.h"
11 #include "base/strings/sys_string_conversions.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/browser.h"
14 #import "chrome/browser/ui/cocoa/extensions/browser_actions_controller.h"
15 #import "chrome/browser/ui/cocoa/extensions/extension_action_context_menu_controller.h"
16 #import "chrome/browser/ui/cocoa/toolbar/toolbar_action_view_delegate_cocoa.h"
17 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
18 #include "grit/theme_resources.h"
19 #include "skia/ext/skia_utils_mac.h"
20 #import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
21 #include "ui/gfx/canvas_skia_paint.h"
22 #include "ui/gfx/image/image.h"
23 #include "ui/gfx/rect.h"
24 #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h"
25
26 NSString* const kBrowserActionButtonDraggingNotification =
27     @"BrowserActionButtonDraggingNotification";
28 NSString* const kBrowserActionButtonDragEndNotification =
29     @"BrowserActionButtonDragEndNotification";
30
31 static const CGFloat kBrowserActionBadgeOriginYOffset = 5;
32 static const CGFloat kAnimationDuration = 0.2;
33 static const CGFloat kMinimumDragDistance = 5;
34
35 // A class to bridge the ToolbarActionViewController and the
36 // BrowserActionButton.
37 class ToolbarActionViewDelegateBridge : public ToolbarActionViewDelegateCocoa {
38  public:
39   ToolbarActionViewDelegateBridge(BrowserActionButton* owner,
40                                   BrowserActionsController* controller);
41   ~ToolbarActionViewDelegateBridge();
42
43   ExtensionActionContextMenuController* menuController() {
44     return menuController_;
45   }
46
47  private:
48   // ToolbarActionViewDelegateCocoa:
49   ToolbarActionViewController* GetPreferredPopupViewController() override;
50   content::WebContents* GetCurrentWebContents() const override;
51   void UpdateState() override;
52   NSPoint GetPopupPoint() override;
53   void SetContextMenuController(
54       ExtensionActionContextMenuController* menuController) override;
55
56   // The owning button. Weak.
57   BrowserActionButton* owner_;
58
59   // The BrowserActionsController that owns the button. Weak.
60   BrowserActionsController* controller_;
61
62   // The context menu controller. Weak.
63   ExtensionActionContextMenuController* menuController_;
64
65   DISALLOW_COPY_AND_ASSIGN(ToolbarActionViewDelegateBridge);
66 };
67
68 ToolbarActionViewDelegateBridge::ToolbarActionViewDelegateBridge(
69     BrowserActionButton* owner,
70     BrowserActionsController* controller)
71     : owner_(owner),
72       controller_(controller),
73       menuController_(nil) {
74 }
75
76 ToolbarActionViewDelegateBridge::~ToolbarActionViewDelegateBridge() {
77 }
78
79 ToolbarActionViewController*
80 ToolbarActionViewDelegateBridge::GetPreferredPopupViewController() {
81   return [owner_ viewController];
82 }
83
84 content::WebContents* ToolbarActionViewDelegateBridge::GetCurrentWebContents()
85     const {
86   return [controller_ currentWebContents];
87 }
88
89 void ToolbarActionViewDelegateBridge::UpdateState() {
90   [owner_ updateState];
91 }
92
93 NSPoint ToolbarActionViewDelegateBridge::GetPopupPoint() {
94   return [controller_ popupPointForId:[owner_ viewController]->GetId()];
95 }
96
97 void ToolbarActionViewDelegateBridge::SetContextMenuController(
98     ExtensionActionContextMenuController* menuController) {
99   menuController_ = menuController;
100 }
101
102 @interface BrowserActionCell (Internals)
103 - (void)drawBadgeWithinFrame:(NSRect)frame
104               forWebContents:(content::WebContents*)webContents;
105 @end
106
107 @interface BrowserActionButton (Private)
108 - (void)endDrag;
109 @end
110
111 @implementation BrowserActionButton
112
113 @synthesize isBeingDragged = isBeingDragged_;
114
115 + (Class)cellClass {
116   return [BrowserActionCell class];
117 }
118
119 - (id)initWithFrame:(NSRect)frame
120      viewController:(scoped_ptr<ToolbarActionViewController>)viewController
121          controller:(BrowserActionsController*)controller {
122   if ((self = [super initWithFrame:frame])) {
123     BrowserActionCell* cell = [[[BrowserActionCell alloc] init] autorelease];
124     // [NSButton setCell:] warns to NOT use setCell: other than in the
125     // initializer of a control.  However, we are using a basic
126     // NSButton whose initializer does not take an NSCell as an
127     // object.  To honor the assumed semantics, we do nothing with
128     // NSButton between alloc/init and setCell:.
129     [self setCell:cell];
130
131     browserActionsController_ = controller;
132     viewControllerDelegate_.reset(
133         new ToolbarActionViewDelegateBridge(self, controller));
134     viewController_ = viewController.Pass();
135     viewController_->SetDelegate(viewControllerDelegate_.get());
136
137     [cell setBrowserActionsController:controller];
138     [cell setViewController:viewController_.get()];
139     [cell
140         accessibilitySetOverrideValue:base::SysUTF16ToNSString(
141             viewController_->GetAccessibleName([controller currentWebContents]))
142         forAttribute:NSAccessibilityDescriptionAttribute];
143     [cell setImageID:IDR_BROWSER_ACTION
144       forButtonState:image_button_cell::kDefaultState];
145     [cell setImageID:IDR_BROWSER_ACTION_H
146       forButtonState:image_button_cell::kHoverState];
147     [cell setImageID:IDR_BROWSER_ACTION_P
148       forButtonState:image_button_cell::kPressedState];
149     [cell setImageID:IDR_BROWSER_ACTION
150       forButtonState:image_button_cell::kDisabledState];
151
152     [self setTitle:@""];
153     [self setButtonType:NSMomentaryChangeButton];
154     [self setShowsBorderOnlyWhileMouseInside:YES];
155
156     base::scoped_nsobject<NSMenu> contextMenu(
157         [[NSMenu alloc] initWithTitle:@""]);
158     [contextMenu setDelegate:self];
159     [self setMenu:contextMenu];
160
161     moveAnimation_.reset([[NSViewAnimation alloc] init]);
162     [moveAnimation_ gtm_setDuration:kAnimationDuration
163                           eventMask:NSLeftMouseUpMask];
164     [moveAnimation_ setAnimationBlockingMode:NSAnimationNonblocking];
165
166     [self updateState];
167   }
168
169   return self;
170 }
171
172 - (BOOL)acceptsFirstResponder {
173   return YES;
174 }
175
176 - (void)mouseDown:(NSEvent*)theEvent {
177   NSPoint location = [self convertPoint:[theEvent locationInWindow]
178                                fromView:nil];
179   if (NSPointInRect(location, [self bounds])) {
180     [[self cell] setHighlighted:YES];
181     dragCouldStart_ = YES;
182     dragStartPoint_ = [theEvent locationInWindow];
183   }
184 }
185
186 - (void)mouseDragged:(NSEvent*)theEvent {
187   if (!dragCouldStart_)
188     return;
189
190   if (!isBeingDragged_) {
191     // Don't initiate a drag until it moves at least kMinimumDragDistance.
192     NSPoint currentPoint = [theEvent locationInWindow];
193     CGFloat dx = currentPoint.x - dragStartPoint_.x;
194     CGFloat dy = currentPoint.y - dragStartPoint_.y;
195     if (dx*dx + dy*dy < kMinimumDragDistance*kMinimumDragDistance)
196       return;
197
198     // The start of a drag. Position the button above all others.
199     [[self superview] addSubview:self positioned:NSWindowAbove relativeTo:nil];
200   }
201   isBeingDragged_ = YES;
202   NSRect buttonFrame = [self frame];
203   // TODO(andybons): Constrain the buttons to be within the container.
204   // Clamp the button to be within its superview along the X-axis.
205   buttonFrame.origin.x += [theEvent deltaX];
206   [self setFrame:buttonFrame];
207   [self setNeedsDisplay:YES];
208   [[NSNotificationCenter defaultCenter]
209       postNotificationName:kBrowserActionButtonDraggingNotification
210       object:self];
211 }
212
213 - (void)mouseUp:(NSEvent*)theEvent {
214   dragCouldStart_ = NO;
215   // There are non-drag cases where a mouseUp: may happen
216   // (e.g. mouse-down, cmd-tab to another application, move mouse,
217   // mouse-up).
218   NSPoint location = [self convertPoint:[theEvent locationInWindow]
219                                fromView:nil];
220   if (NSPointInRect(location, [self bounds]) && !isBeingDragged_) {
221     // Only perform the click if we didn't drag the button.
222     [self performClick:self];
223   } else {
224     // Make sure an ESC to end a drag doesn't trigger 2 endDrags.
225     if (isBeingDragged_) {
226       [self endDrag];
227     } else {
228       [super mouseUp:theEvent];
229     }
230   }
231 }
232
233 - (void)endDrag {
234   isBeingDragged_ = NO;
235   [[NSNotificationCenter defaultCenter]
236       postNotificationName:kBrowserActionButtonDragEndNotification object:self];
237   [[self cell] setHighlighted:NO];
238 }
239
240 - (void)setFrame:(NSRect)frameRect animate:(BOOL)animate {
241   if (!animate) {
242     [self setFrame:frameRect];
243   } else {
244     if ([moveAnimation_ isAnimating])
245       [moveAnimation_ stopAnimation];
246
247     NSDictionary* animationDictionary =
248         [NSDictionary dictionaryWithObjectsAndKeys:
249             self, NSViewAnimationTargetKey,
250             [NSValue valueWithRect:[self frame]], NSViewAnimationStartFrameKey,
251             [NSValue valueWithRect:frameRect], NSViewAnimationEndFrameKey,
252             nil];
253     [moveAnimation_ setViewAnimations:
254         [NSArray arrayWithObject:animationDictionary]];
255     [moveAnimation_ startAnimation];
256   }
257 }
258
259 - (void)updateState {
260   content::WebContents* webContents =
261       [browserActionsController_ currentWebContents];
262   if (!webContents)
263     return;
264
265   base::string16 tooltip = viewController_->GetTooltip(webContents);
266   [self setToolTip:(tooltip.empty() ? nil : base::SysUTF16ToNSString(tooltip))];
267
268   gfx::Image image = viewController_->GetIcon(webContents);
269
270   if (!image.IsEmpty())
271     [self setImage:image.ToNSImage()];
272
273   [self setEnabled:viewController_->IsEnabled(webContents)];
274
275   [self setNeedsDisplay:YES];
276 }
277
278 - (BOOL)isAnimating {
279   return [moveAnimation_ isAnimating];
280 }
281
282 - (ToolbarActionViewController*)viewController {
283   return viewController_.get();
284 }
285
286 - (NSImage*)compositedImage {
287   NSRect bounds = [self bounds];
288   NSImage* image = [[[NSImage alloc] initWithSize:bounds.size] autorelease];
289   [image lockFocus];
290
291   [[NSColor clearColor] set];
292   NSRectFill(bounds);
293
294   NSImage* actionImage = [self image];
295   const NSSize imageSize = [actionImage size];
296   const NSRect imageRect =
297       NSMakeRect(std::floor((NSWidth(bounds) - imageSize.width) / 2.0),
298                  std::floor((NSHeight(bounds) - imageSize.height) / 2.0),
299                  imageSize.width, imageSize.height);
300   [actionImage drawInRect:imageRect
301                  fromRect:NSZeroRect
302                 operation:NSCompositeSourceOver
303                  fraction:1.0
304            respectFlipped:YES
305                     hints:nil];
306
307   bounds.origin.y += kBrowserActionBadgeOriginYOffset;
308   [[self cell] drawBadgeWithinFrame:bounds
309                      forWebContents:
310                             [browserActionsController_ currentWebContents]];
311
312   [image unlockFocus];
313   return image;
314 }
315
316 - (void)menuNeedsUpdate:(NSMenu*)menu {
317   [menu removeAllItems];
318   // |menuController()| can be nil if we don't show context menus for the given
319   // action.
320   if (viewControllerDelegate_->menuController())
321     [viewControllerDelegate_->menuController() populateMenu:menu];
322 }
323
324 @end
325
326 @implementation BrowserActionCell
327
328 @synthesize browserActionsController = browserActionsController_;
329 @synthesize viewController = viewController_;
330
331 - (void)drawBadgeWithinFrame:(NSRect)frame
332               forWebContents:(content::WebContents*)webContents {
333   gfx::CanvasSkiaPaint canvas(frame, false);
334   canvas.set_composite_alpha(true);
335   gfx::Rect boundingRect(NSRectToCGRect(frame));
336   viewController_->PaintExtra(&canvas, boundingRect, webContents);
337 }
338
339 - (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView {
340   gfx::ScopedNSGraphicsContextSaveGState scopedGState;
341   [super drawWithFrame:cellFrame inView:controlView];
342   DCHECK(viewController_);
343   content::WebContents* webContents =
344       [browserActionsController_ currentWebContents];
345   bool enabled = viewController_->IsEnabled(webContents);
346   const NSSize imageSize = self.image.size;
347   const NSRect imageRect =
348       NSMakeRect(std::floor((NSWidth(cellFrame) - imageSize.width) / 2.0),
349                  std::floor((NSHeight(cellFrame) - imageSize.height) / 2.0),
350                  imageSize.width, imageSize.height);
351   [self.image drawInRect:imageRect
352                 fromRect:NSZeroRect
353                operation:NSCompositeSourceOver
354                 fraction:enabled ? 1.0 : 0.4
355           respectFlipped:YES
356                    hints:nil];
357
358   cellFrame.origin.y += kBrowserActionBadgeOriginYOffset;
359   [self drawBadgeWithinFrame:cellFrame
360               forWebContents:webContents];
361 }
362
363 @end