blob: 4e91304d984bd5a5db803745d28ff5f043b782cd [file] [log] [blame]
// Copyright (c) 2012 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.
#import "chrome/browser/ui/cocoa/extensions/browser_actions_controller.h"
#include <cmath>
#include <string>
#include "base/strings/sys_string_conversions.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/extension_action.h"
#include "chrome/browser/extensions/extension_action_manager.h"
#include "chrome/browser/extensions/extension_toolbar_model.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#import "chrome/browser/ui/cocoa/extensions/browser_action_button.h"
#import "chrome/browser/ui/cocoa/extensions/browser_actions_container_view.h"
#import "chrome/browser/ui/cocoa/extensions/extension_popup_controller.h"
#import "chrome/browser/ui/cocoa/image_button_cell.h"
#import "chrome/browser/ui/cocoa/menu_button.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/extensions/api/extension_action/action_info.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_source.h"
#include "extensions/browser/extension_registry.h"
#include "grit/theme_resources.h"
#import "third_party/google_toolbox_for_mac/src/AppKit/GTMNSAnimation+Duration.h"
using extensions::Extension;
using extensions::ExtensionList;
NSString* const kBrowserActionVisibilityChangedNotification =
@"BrowserActionVisibilityChangedNotification";
namespace {
const CGFloat kAnimationDuration = 0.2;
const CGFloat kChevronWidth = 18;
// Since the container is the maximum height of the toolbar, we have
// to move the buttons up by this amount in order to have them look
// vertically centered within the toolbar.
const CGFloat kBrowserActionOriginYOffset = 5.0;
// The size of each button on the toolbar.
const CGFloat kBrowserActionHeight = 29.0;
const CGFloat kBrowserActionWidth = 29.0;
// The padding between browser action buttons.
const CGFloat kBrowserActionButtonPadding = 2.0;
// Padding between Omnibox and first button. Since the buttons have a
// pixel of internal padding, this needs an extra pixel.
const CGFloat kBrowserActionLeftPadding = kBrowserActionButtonPadding + 1.0;
// How far to inset from the bottom of the view to get the top border
// of the popup 2px below the bottom of the Omnibox.
const CGFloat kBrowserActionBubbleYOffset = 3.0;
} // namespace
@interface BrowserActionsController(Private)
// Used during initialization to create the BrowserActionButton objects from the
// stored toolbar model.
- (void)createButtons;
// Creates and then adds the given extension's action button to the container
// at the given index within the container. It does not affect the toolbar model
// object since it is called when the toolbar model changes.
- (void)createActionButtonForExtension:(const Extension*)extension
withIndex:(NSUInteger)index;
// Removes an action button for the given extension from the container. This
// method also does not affect the underlying toolbar model since it is called
// when the toolbar model changes.
- (void)removeActionButtonForExtension:(const Extension*)extension;
// Useful in the case of a Browser Action being added/removed from the middle of
// the container, this method repositions each button according to the current
// toolbar model.
- (void)positionActionButtonsAndAnimate:(BOOL)animate;
// During container resizing, buttons become more transparent as they are pushed
// off the screen. This method updates each button's opacity determined by the
// position of the button.
- (void)updateButtonOpacity;
// Returns the existing button with the given extension backing it; nil if it
// cannot be found or the extension's ID is invalid.
- (BrowserActionButton*)buttonForExtension:(const Extension*)extension;
// Returns the preferred width of the container given the number of visible
// buttons |buttonCount|.
- (CGFloat)containerWidthWithButtonCount:(NSUInteger)buttonCount;
// Returns the number of buttons that can fit in the container according to its
// current size.
- (NSUInteger)containerButtonCapacity;
// Notification handlers for events registered by the class.
// Updates each button's opacity, the cursor rects and chevron position.
- (void)containerFrameChanged:(NSNotification*)notification;
// Hides the chevron and unhides every hidden button so that dragging the
// container out smoothly shows the Browser Action buttons.
- (void)containerDragStart:(NSNotification*)notification;
// Sends a notification for the toolbar to reposition surrounding UI elements.
- (void)containerDragging:(NSNotification*)notification;
// Determines which buttons need to be hidden based on the new size, hides them
// and updates the chevron overflow menu. Also fires a notification to let the
// toolbar know that the drag has finished.
- (void)containerDragFinished:(NSNotification*)notification;
// Adjusts the position of the surrounding action buttons depending on where the
// button is within the container.
- (void)actionButtonDragging:(NSNotification*)notification;
// Updates the position of the Browser Actions within the container. This fires
// when _any_ Browser Action button is done dragging to keep all open windows in
// sync visually.
- (void)actionButtonDragFinished:(NSNotification*)notification;
// Moves the given button both visually and within the toolbar model to the
// specified index.
- (void)moveButton:(BrowserActionButton*)button
toIndex:(NSUInteger)index
animate:(BOOL)animate;
// Handles when the given BrowserActionButton object is clicked and whether
// it should grant tab permissions. API-simulated clicks should not grant.
- (BOOL)browserActionClicked:(BrowserActionButton*)button
shouldGrant:(BOOL)shouldGrant;
- (BOOL)browserActionClicked:(BrowserActionButton*)button;
// The reason |frame| is specified in these chevron functions is because the
// container may be animating and the end frame of the animation should be
// passed instead of the current frame (which may be off and cause the chevron
// to jump at the end of its animation).
// Shows the overflow chevron button depending on whether there are any hidden
// extensions within the frame given.
- (void)showChevronIfNecessaryInFrame:(NSRect)frame animate:(BOOL)animate;
// Moves the chevron to its correct position within |frame|.
- (void)updateChevronPositionInFrame:(NSRect)frame;
// Shows or hides the chevron, animating as specified by |animate|.
- (void)setChevronHidden:(BOOL)hidden
inFrame:(NSRect)frame
animate:(BOOL)animate;
// Handles when a menu item within the chevron overflow menu is selected.
- (void)chevronItemSelected:(id)menuItem;
// Updates the container's grippy cursor based on the number of hidden buttons.
- (void)updateGrippyCursors;
// Returns the ID of the currently selected tab or -1 if none exists.
- (int)currentTabId;
@end
// A helper class to proxy extension notifications to the view controller's
// appropriate methods.
class ExtensionServiceObserverBridge
: public content::NotificationObserver,
public extensions::ExtensionToolbarModel::Observer {
public:
ExtensionServiceObserverBridge(BrowserActionsController* owner,
Browser* browser)
: owner_(owner), browser_(browser) {
registrar_.Add(this,
extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
content::Source<Profile>(browser->profile()));
registrar_.Add(
this,
extensions::NOTIFICATION_EXTENSION_COMMAND_BROWSER_ACTION_MAC,
content::Source<Profile>(browser->profile()));
}
// Overridden from content::NotificationObserver.
void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) override {
switch (type) {
case extensions::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE: {
ExtensionPopupController* popup = [ExtensionPopupController popup];
if (popup && ![popup isClosing])
[popup close];
break;
}
case extensions::NOTIFICATION_EXTENSION_COMMAND_BROWSER_ACTION_MAC: {
std::pair<const std::string, gfx::NativeWindow>* payload =
content::Details<std::pair<const std::string, gfx::NativeWindow> >(
details).ptr();
std::string extension_id = payload->first;
gfx::NativeWindow window = payload->second;
if (window != browser_->window()->GetNativeWindow())
break;
[owner_ activateBrowserAction:extension_id];
break;
}
default:
NOTREACHED() << L"Unexpected notification";
}
}
// extensions::ExtensionToolbarModel::Observer implementation.
void ToolbarExtensionAdded(const Extension* extension, int index) override {
[owner_ createActionButtonForExtension:extension withIndex:index];
[owner_ resizeContainerAndAnimate:NO];
}
void ToolbarExtensionRemoved(const Extension* extension) override {
[owner_ removeActionButtonForExtension:extension];
[owner_ resizeContainerAndAnimate:NO];
}
void ToolbarExtensionMoved(const Extension* extension, int index) override {}
void ToolbarExtensionUpdated(const Extension* extension) override {
BrowserActionButton* button = [owner_ buttonForExtension:extension];
if (button)
[button updateState];
}
bool ShowExtensionActionPopup(const Extension* extension,
bool grant_active_tab) override {
// Do not override other popups and only show in active window.
ExtensionPopupController* popup = [ExtensionPopupController popup];
if (popup || !browser_->window()->IsActive())
return false;
BrowserActionButton* button = [owner_ buttonForExtension:extension];
return button && [owner_ browserActionClicked:button
shouldGrant:grant_active_tab];
}
void ToolbarVisibleCountChanged() override {}
void ToolbarHighlightModeChanged(bool is_highlighting) override {}
Browser* GetBrowser() override { return browser_; }
private:
// The object we need to inform when we get a notification. Weak. Owns us.
BrowserActionsController* owner_;
// The browser we listen for events from. Weak.
Browser* browser_;
// Used for registering to receive notifications and automatic clean up.
content::NotificationRegistrar registrar_;
DISALLOW_COPY_AND_ASSIGN(ExtensionServiceObserverBridge);
};
@implementation BrowserActionsController
@synthesize containerView = containerView_;
#pragma mark -
#pragma mark Public Methods
- (id)initWithBrowser:(Browser*)browser
containerView:(BrowserActionsContainerView*)container {
DCHECK(browser && container);
if ((self = [super init])) {
browser_ = browser;
profile_ = browser->profile();
observer_.reset(new ExtensionServiceObserverBridge(self, browser_));
toolbarModel_ = extensions::ExtensionToolbarModel::Get(profile_);
if (toolbarModel_)
toolbarModel_->AddObserver(observer_.get());
containerView_ = container;
[containerView_ setPostsFrameChangedNotifications:YES];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(containerFrameChanged:)
name:NSViewFrameDidChangeNotification
object:containerView_];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(containerDragStart:)
name:kBrowserActionGrippyDragStartedNotification
object:containerView_];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(containerDragging:)
name:kBrowserActionGrippyDraggingNotification
object:containerView_];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(containerDragFinished:)
name:kBrowserActionGrippyDragFinishedNotification
object:containerView_];
// Listen for a finished drag from any button to make sure each open window
// stays in sync.
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(actionButtonDragFinished:)
name:kBrowserActionButtonDragEndNotification
object:nil];
chevronAnimation_.reset([[NSViewAnimation alloc] init]);
[chevronAnimation_ gtm_setDuration:kAnimationDuration
eventMask:NSLeftMouseUpMask];
[chevronAnimation_ setAnimationBlockingMode:NSAnimationNonblocking];
hiddenButtons_.reset([[NSMutableArray alloc] init]);
buttons_.reset([[NSMutableDictionary alloc] init]);
[self createButtons];
[self showChevronIfNecessaryInFrame:[containerView_ frame] animate:NO];
[self updateGrippyCursors];
[container setResizable:!profile_->IsOffTheRecord()];
}
return self;
}
- (void)dealloc {
if (toolbarModel_)
toolbarModel_->RemoveObserver(observer_.get());
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
- (void)update {
for (BrowserActionButton* button in [buttons_ allValues]) {
[button setTabId:[self currentTabId]];
[button updateState];
}
}
- (NSUInteger)buttonCount {
return [buttons_ count];
}
- (NSUInteger)visibleButtonCount {
return [self buttonCount] - [hiddenButtons_ count];
}
- (void)resizeContainerAndAnimate:(BOOL)animate {
int iconCount = toolbarModel_->GetVisibleIconCount();
if (iconCount < 0) // If no buttons are hidden.
iconCount = [self buttonCount];
[containerView_ resizeToWidth:[self containerWidthWithButtonCount:iconCount]
animate:animate];
NSRect frame = animate ? [containerView_ animationEndFrame] :
[containerView_ frame];
[self showChevronIfNecessaryInFrame:frame animate:animate];
if (!animate) {
[[NSNotificationCenter defaultCenter]
postNotificationName:kBrowserActionVisibilityChangedNotification
object:self];
}
}
- (NSView*)browserActionViewForExtension:(const Extension*)extension {
for (BrowserActionButton* button in [buttons_ allValues]) {
if ([button extension] == extension)
return button;
}
NOTREACHED();
return nil;
}
- (CGFloat)savedWidth {
if (!toolbarModel_)
return 0;
int savedButtonCount = toolbarModel_->GetVisibleIconCount();
if (savedButtonCount < 0 || // all icons are visible
static_cast<NSUInteger>(savedButtonCount) > [self buttonCount])
savedButtonCount = [self buttonCount];
return [self containerWidthWithButtonCount:savedButtonCount];
}
- (NSPoint)popupPointForBrowserAction:(const Extension*)extension {
if (!extensions::ExtensionActionManager::Get(profile_)->
GetBrowserAction(*extension)) {
return NSZeroPoint;
}
NSButton* button = [self buttonForExtension:extension];
if (!button)
return NSZeroPoint;
if ([hiddenButtons_ containsObject:button])
button = chevronMenuButton_.get();
// Anchor point just above the center of the bottom.
const NSRect bounds = [button bounds];
DCHECK([button isFlipped]);
NSPoint anchor = NSMakePoint(NSMidX(bounds),
NSMaxY(bounds) - kBrowserActionBubbleYOffset);
return [button convertPoint:anchor toView:nil];
}
- (BOOL)chevronIsHidden {
if (!chevronMenuButton_.get())
return YES;
if (![chevronAnimation_ isAnimating])
return [chevronMenuButton_ isHidden];
DCHECK([[chevronAnimation_ viewAnimations] count] > 0);
// The chevron is animating in or out. Determine which one and have the return
// value reflect where the animation is headed.
NSString* effect = [[[chevronAnimation_ viewAnimations] objectAtIndex:0]
valueForKey:NSViewAnimationEffectKey];
if (effect == NSViewAnimationFadeInEffect) {
return NO;
} else if (effect == NSViewAnimationFadeOutEffect) {
return YES;
}
NOTREACHED();
return YES;
}
- (void)activateBrowserAction:(const std::string&)extension_id {
const Extension* extension = extensions::ExtensionRegistry::Get(
browser_->profile())->enabled_extensions().GetByID(extension_id);
if (!extension)
return;
BrowserActionButton* button = [self buttonForExtension:extension];
// |button| can be nil when the browser action has its button hidden.
if (button)
[self browserActionClicked:button];
}
#pragma mark -
#pragma mark NSMenuDelegate
- (void)menuNeedsUpdate:(NSMenu*)menu {
[menu removeAllItems];
// See menu_button.h for documentation on why this is needed.
[menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
for (BrowserActionButton* button in hiddenButtons_.get()) {
NSString* name = base::SysUTF8ToNSString([button extension]->name());
NSMenuItem* item =
[menu addItemWithTitle:name
action:@selector(chevronItemSelected:)
keyEquivalent:@""];
[item setRepresentedObject:button];
[item setImage:[button compositedImage]];
[item setTarget:self];
[item setEnabled:[button isEnabled]];
}
}
#pragma mark -
#pragma mark Private Methods
- (void)createButtons {
if (!toolbarModel_)
return;
NSUInteger i = 0;
for (ExtensionList::const_iterator iter =
toolbarModel_->toolbar_items().begin();
iter != toolbarModel_->toolbar_items().end(); ++iter) {
[self createActionButtonForExtension:iter->get() withIndex:i++];
}
CGFloat width = [self savedWidth];
[containerView_ resizeToWidth:width animate:NO];
}
- (void)createActionButtonForExtension:(const Extension*)extension
withIndex:(NSUInteger)index {
// Show the container if it's the first button. Otherwise it will be shown
// already.
if ([self buttonCount] == 0)
[containerView_ setHidden:NO];
NSRect buttonFrame = NSMakeRect(0.0, kBrowserActionOriginYOffset,
kBrowserActionWidth, kBrowserActionHeight);
BrowserActionButton* newButton =
[[[BrowserActionButton alloc]
initWithFrame:buttonFrame
extension:extension
browser:browser_
tabId:[self currentTabId]] autorelease];
[newButton setTarget:self];
[newButton setAction:@selector(browserActionClicked:)];
NSString* buttonKey = base::SysUTF8ToNSString(extension->id());
if (!buttonKey)
return;
[buttons_ setObject:newButton forKey:buttonKey];
[self positionActionButtonsAndAnimate:NO];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(actionButtonDragging:)
name:kBrowserActionButtonDraggingNotification
object:newButton];
[containerView_ setMaxWidth:
[self containerWidthWithButtonCount:[self buttonCount]]];
[containerView_ setNeedsDisplay:YES];
}
- (void)removeActionButtonForExtension:(const Extension*)extension {
NSString* buttonKey = base::SysUTF8ToNSString(extension->id());
if (!buttonKey)
return;
BrowserActionButton* button = [buttons_ objectForKey:buttonKey];
[button removeFromSuperview];
// It may or may not be hidden, but it won't matter to NSMutableArray either
// way.
[hiddenButtons_ removeObject:button];
[buttons_ removeObjectForKey:buttonKey];
if ([self buttonCount] == 0) {
// No more buttons? Hide the container.
[containerView_ setHidden:YES];
} else {
[self positionActionButtonsAndAnimate:NO];
}
[containerView_ setMaxWidth:
[self containerWidthWithButtonCount:[self buttonCount]]];
[containerView_ setNeedsDisplay:YES];
}
- (void)positionActionButtonsAndAnimate:(BOOL)animate {
NSUInteger i = 0;
for (ExtensionList::const_iterator iter =
toolbarModel_->toolbar_items().begin();
iter != toolbarModel_->toolbar_items().end(); ++iter) {
BrowserActionButton* button = [self buttonForExtension:(iter->get())];
if (!button)
continue;
if (![button isBeingDragged])
[self moveButton:button toIndex:i animate:animate];
++i;
}
}
- (void)updateButtonOpacity {
for (BrowserActionButton* button in [buttons_ allValues]) {
NSRect buttonFrame = [button frame];
if (NSContainsRect([containerView_ bounds], buttonFrame)) {
if ([button alphaValue] != 1.0)
[button setAlphaValue:1.0];
continue;
}
CGFloat intersectionWidth =
NSWidth(NSIntersectionRect([containerView_ bounds], buttonFrame));
CGFloat alpha = std::max(static_cast<CGFloat>(0.0),
intersectionWidth / NSWidth(buttonFrame));
[button setAlphaValue:alpha];
[button setNeedsDisplay:YES];
}
}
- (BrowserActionButton*)buttonForExtension:(const Extension*)extension {
NSString* extensionId = base::SysUTF8ToNSString(extension->id());
DCHECK(extensionId);
if (!extensionId)
return nil;
return [buttons_ objectForKey:extensionId];
}
- (CGFloat)containerWidthWithButtonCount:(NSUInteger)buttonCount {
// Left-side padding which works regardless of whether a button or
// chevron leads.
CGFloat width = kBrowserActionLeftPadding;
// Include the buttons and padding between.
if (buttonCount > 0) {
width += buttonCount * kBrowserActionWidth;
width += (buttonCount - 1) * kBrowserActionButtonPadding;
}
// Make room for the chevron if any buttons are hidden.
if ([self buttonCount] != [self visibleButtonCount]) {
// Chevron and buttons both include 1px padding w/in their bounds,
// so this leaves 2px between the last browser action and chevron,
// and also works right if the chevron is the only button.
width += kChevronWidth;
}
return width;
}
- (NSUInteger)containerButtonCapacity {
// Edge-to-edge span of the browser action buttons.
CGFloat actionSpan = [self savedWidth] - kBrowserActionLeftPadding;
// Add in some padding for the browser action on the end, then
// divide out to get the number of action buttons that fit.
return (actionSpan + kBrowserActionButtonPadding) /
(kBrowserActionWidth + kBrowserActionButtonPadding);
}
- (void)containerFrameChanged:(NSNotification*)notification {
[self updateButtonOpacity];
[[containerView_ window] invalidateCursorRectsForView:containerView_];
[self updateChevronPositionInFrame:[containerView_ frame]];
}
- (void)containerDragStart:(NSNotification*)notification {
[self setChevronHidden:YES inFrame:[containerView_ frame] animate:YES];
while([hiddenButtons_ count] > 0) {
BrowserActionButton* button = [hiddenButtons_ objectAtIndex:0];
[button setAlphaValue:1.0];
[containerView_ addSubview:button];
[hiddenButtons_ removeObjectAtIndex:0];
}
}
- (void)containerDragging:(NSNotification*)notification {
[[NSNotificationCenter defaultCenter]
postNotificationName:kBrowserActionGrippyDraggingNotification
object:self];
}
- (void)containerDragFinished:(NSNotification*)notification {
for (ExtensionList::const_iterator iter =
toolbarModel_->toolbar_items().begin();
iter != toolbarModel_->toolbar_items().end(); ++iter) {
BrowserActionButton* button = [self buttonForExtension:(iter->get())];
NSRect buttonFrame = [button frame];
if (NSContainsRect([containerView_ bounds], buttonFrame))
continue;
CGFloat intersectionWidth =
NSWidth(NSIntersectionRect([containerView_ bounds], buttonFrame));
// Pad the threshold by 5 pixels in order to have the buttons hide more
// easily.
if (([containerView_ grippyPinned] && intersectionWidth > 0) ||
(intersectionWidth <= (NSWidth(buttonFrame) / 2) + 5.0)) {
[button setAlphaValue:0.0];
[button removeFromSuperview];
[hiddenButtons_ addObject:button];
}
}
[self updateGrippyCursors];
toolbarModel_->SetVisibleIconCount([self visibleButtonCount]);
[[NSNotificationCenter defaultCenter]
postNotificationName:kBrowserActionGrippyDragFinishedNotification
object:self];
}
- (void)actionButtonDragging:(NSNotification*)notification {
if (![self chevronIsHidden])
[self setChevronHidden:YES inFrame:[containerView_ frame] animate:YES];
// Determine what index the dragged button should lie in, alter the model and
// reposition the buttons.
CGFloat dragThreshold = std::floor(kBrowserActionWidth / 2);
BrowserActionButton* draggedButton = [notification object];
NSRect draggedButtonFrame = [draggedButton frame];
NSUInteger index = 0;
for (ExtensionList::const_iterator iter =
toolbarModel_->toolbar_items().begin();
iter != toolbarModel_->toolbar_items().end(); ++iter) {
BrowserActionButton* button = [self buttonForExtension:(iter->get())];
CGFloat intersectionWidth =
NSWidth(NSIntersectionRect(draggedButtonFrame, [button frame]));
if (intersectionWidth > dragThreshold && button != draggedButton &&
![button isAnimating] && index < [self visibleButtonCount]) {
toolbarModel_->MoveExtensionIcon([draggedButton extension]->id(), index);
[self positionActionButtonsAndAnimate:YES];
return;
}
++index;
}
}
- (void)actionButtonDragFinished:(NSNotification*)notification {
[self showChevronIfNecessaryInFrame:[containerView_ frame] animate:YES];
[self positionActionButtonsAndAnimate:YES];
}
- (void)moveButton:(BrowserActionButton*)button
toIndex:(NSUInteger)index
animate:(BOOL)animate {
CGFloat xOffset = kBrowserActionLeftPadding +
(index * (kBrowserActionWidth + kBrowserActionButtonPadding));
NSRect buttonFrame = [button frame];
buttonFrame.origin.x = xOffset;
[button setFrame:buttonFrame animate:animate];
if (index < [self containerButtonCapacity]) {
// Make sure the button is within the visible container.
if ([button superview] != containerView_) {
[containerView_ addSubview:button];
[button setAlphaValue:1.0];
[hiddenButtons_ removeObjectIdenticalTo:button];
}
} else if (![hiddenButtons_ containsObject:button]) {
[hiddenButtons_ addObject:button];
[button removeFromSuperview];
[button setAlphaValue:0.0];
}
}
- (BOOL)browserActionClicked:(BrowserActionButton*)button
shouldGrant:(BOOL)shouldGrant {
const Extension* extension = [button extension];
switch (extensions::ExtensionActionAPI::Get(profile_)->ExecuteExtensionAction(
extension, browser_, shouldGrant)) {
case ExtensionAction::ACTION_NONE:
break;
case ExtensionAction::ACTION_SHOW_POPUP: {
GURL popupUrl = extensions::ExtensionActionManager::Get(profile_)->
GetBrowserAction(*extension)->GetPopupUrl([self currentTabId]);
NSPoint arrowPoint = [self popupPointForBrowserAction:extension];
[ExtensionPopupController showURL:popupUrl
inBrowser:browser_
anchoredAt:arrowPoint
arrowLocation:info_bubble::kTopRight
devMode:NO];
return YES;
}
}
return NO;
}
- (BOOL)browserActionClicked:(BrowserActionButton*)button {
return [self browserActionClicked:button
shouldGrant:YES];
}
- (void)showChevronIfNecessaryInFrame:(NSRect)frame animate:(BOOL)animate {
[self setChevronHidden:([self buttonCount] == [self visibleButtonCount])
inFrame:frame
animate:animate];
}
- (void)updateChevronPositionInFrame:(NSRect)frame {
CGFloat xPos = NSWidth(frame) - kChevronWidth;
NSRect buttonFrame = NSMakeRect(xPos,
kBrowserActionOriginYOffset,
kChevronWidth,
kBrowserActionHeight);
[chevronMenuButton_ setFrame:buttonFrame];
}
- (void)setChevronHidden:(BOOL)hidden
inFrame:(NSRect)frame
animate:(BOOL)animate {
if (hidden == [self chevronIsHidden])
return;
if (!chevronMenuButton_.get()) {
chevronMenuButton_.reset([[MenuButton alloc] init]);
[chevronMenuButton_ setOpenMenuOnClick:YES];
[chevronMenuButton_ setBordered:NO];
[chevronMenuButton_ setShowsBorderOnlyWhileMouseInside:YES];
[[chevronMenuButton_ cell] setImageID:IDR_BROWSER_ACTIONS_OVERFLOW
forButtonState:image_button_cell::kDefaultState];
[[chevronMenuButton_ cell] setImageID:IDR_BROWSER_ACTIONS_OVERFLOW_H
forButtonState:image_button_cell::kHoverState];
[[chevronMenuButton_ cell] setImageID:IDR_BROWSER_ACTIONS_OVERFLOW_P
forButtonState:image_button_cell::kPressedState];
overflowMenu_.reset([[NSMenu alloc] initWithTitle:@""]);
[overflowMenu_ setAutoenablesItems:NO];
[overflowMenu_ setDelegate:self];
[chevronMenuButton_ setAttachedMenu:overflowMenu_];
[containerView_ addSubview:chevronMenuButton_];
}
[self updateChevronPositionInFrame:frame];
// Stop any running animation.
[chevronAnimation_ stopAnimation];
if (!animate) {
[chevronMenuButton_ setHidden:hidden];
return;
}
NSDictionary* animationDictionary;
if (hidden) {
animationDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
chevronMenuButton_.get(), NSViewAnimationTargetKey,
NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey,
nil];
} else {
[chevronMenuButton_ setHidden:NO];
animationDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
chevronMenuButton_.get(), NSViewAnimationTargetKey,
NSViewAnimationFadeInEffect, NSViewAnimationEffectKey,
nil];
}
[chevronAnimation_ setViewAnimations:
[NSArray arrayWithObject:animationDictionary]];
[chevronAnimation_ startAnimation];
}
- (void)chevronItemSelected:(id)menuItem {
[self browserActionClicked:[menuItem representedObject]];
}
- (void)updateGrippyCursors {
[containerView_ setCanDragLeft:[hiddenButtons_ count] > 0];
[containerView_ setCanDragRight:[self visibleButtonCount] > 0];
[[containerView_ window] invalidateCursorRectsForView:containerView_];
}
- (int)currentTabId {
content::WebContents* active_tab =
browser_->tab_strip_model()->GetActiveWebContents();
if (!active_tab)
return -1;
return SessionTabHelper::FromWebContents(active_tab)->session_id().id();
}
#pragma mark -
#pragma mark Testing Methods
- (BrowserActionButton*)buttonWithIndex:(NSUInteger)index {
const extensions::ExtensionList& toolbar_items =
toolbarModel_->toolbar_items();
if (index < toolbar_items.size()) {
const Extension* extension = toolbar_items[index].get();
return [buttons_ objectForKey:base::SysUTF8ToNSString(extension->id())];
}
return nil;
}
@end