Logo Search packages:      
Sourcecode: chromium-browser version File versions  Download package

browser_actions_controller.mm

// Copyright (c) 2010 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 "browser_actions_controller.h"

#include <cmath>
#include <string>

#include "app/resource_bundle.h"
#include "base/sys_string_conversions.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/pref_service.h"
#import "chrome/browser/cocoa/extensions/browser_action_button.h"
#import "chrome/browser/cocoa/extensions/browser_actions_container_view.h"
#import "chrome/browser/cocoa/extensions/extension_popup_controller.h"
#import "chrome/browser/cocoa/menu_button.h"
#include "chrome/browser/extensions/extension_browser_event_router.h"
#include "chrome/browser/extensions/extension_host.h"
#include "chrome/browser/extensions/extension_toolbar_model.h"
#include "chrome/browser/extensions/extensions_service.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/common/notification_observer.h"
#include "chrome/common/notification_registrar.h"
#include "chrome/common/pref_names.h"
#include "grit/theme_resources.h"
#import "third_party/GTM/AppKit/GTMNSAnimation+Duration.h"

extern const CGFloat kBrowserActionButtonPadding = 3;

extern const NSString* kBrowserActionVisibilityChangedNotification =
    @"BrowserActionVisibilityChangedNotification";

namespace {
const CGFloat kAnimationDuration = 0.2;
// When determining the opacity during a drag, we artificially reduce the
// distance to the edge in order to make the fade more apparent.
const CGFloat kButtonOpacityLeadPadding = 5.0;
const CGFloat kChevronHeight = 28.0;
const CGFloat kChevronLowerPadding = 5.0;
const CGFloat kChevronRightPadding = 5.0;
const CGFloat kChevronWidth = 14.0;
const CGFloat kContainerPadding = 2.0;
const CGFloat kGrippyXOffset = 8.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:(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:(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)repositionActionButtonsAndAnimate:(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:(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;

// Updates the image associated with the button should it be within the chevron
// menu.
- (void)actionButtonUpdated:(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.
- (void)browserActionClicked:(BrowserActionButton*)button;

// Returns whether the given extension should be displayed. Only displays
// incognito-enabled extensions in incognito mode. Otherwise returns YES.
- (BOOL)shouldDisplayBrowserAction:(Extension*)extension;

// 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;

// Clears and then populates the overflow menu based on the contents of
// |hiddenButtons_|.
- (void)updateOverflowMenu;

// 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 ExtensionsServiceObserverBridge : public NotificationObserver,
                                        public ExtensionToolbarModel::Observer {
 public:
  ExtensionsServiceObserverBridge(BrowserActionsController* owner,
                                  Profile* profile) : owner_(owner) {
    registrar_.Add(this, NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE,
                   Source<Profile>(profile));
  }

  // Overridden from NotificationObserver.
  void Observe(NotificationType type,
               const NotificationSource& source,
               const NotificationDetails& details) {
    switch (type.value) {
      case NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE: {
        ExtensionPopupController* popup = [ExtensionPopupController popup];
        if (popup && ![popup isClosing])
          [popup close];

        break;
      }
      default:
        NOTREACHED() << L"Unexpected notification";
    }
  }

  // ExtensionToolbarModel::Observer implementation.
  void BrowserActionAdded(Extension* extension, int index) {
    [owner_ createActionButtonForExtension:extension withIndex:index];
    [owner_ resizeContainerAndAnimate:NO];
  }

  void BrowserActionRemoved(Extension* extension) {
    [owner_ removeActionButtonForExtension:extension];
    [owner_ resizeContainerAndAnimate:NO];
  }

 private:
  // The object we need to inform when we get a notification. Weak. Owns us.
  BrowserActionsController* owner_;

  // Used for registering to receive notifications and automatic clean up.
  NotificationRegistrar registrar_;

  DISALLOW_COPY_AND_ASSIGN(ExtensionsServiceObserverBridge);
};

@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();

    if (!profile_->GetPrefs()->FindPreference(
        prefs::kBrowserActionContainerWidth))
      [BrowserActionsController registerUserPrefs:profile_->GetPrefs()];

    observer_.reset(new ExtensionsServiceObserverBridge(self, profile_));
    ExtensionsService* extensionsService = profile_->GetExtensionsService();
    // |extensionsService| can be NULL in Incognito.
    if (extensionsService) {
      toolbarModel_ = extensionsService->toolbar_model();
      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];
}

- (MenuButton*)chevronMenuButton {
  return chevronMenuButton_.get();
}

- (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:(Extension*)extension {
  for (BrowserActionButton* button in [buttons_ allValues]) {
    if ([button extension] == extension)
      return button;
  }
  NOTREACHED();
  return nil;
}

- (CGFloat)savedWidth {
  if (!toolbarModel_)
    return 0;
  if (!profile_->GetPrefs()->HasPrefPath(prefs::kExtensionToolbarSize)) {
    // Migration code to the new VisibleIconCount pref.
    // TODO(mpcomplete): remove this at some point.
    double predefinedWidth =
        profile_->GetPrefs()->GetReal(prefs::kBrowserActionContainerWidth);
    if (predefinedWidth != 0) {
      int iconWidth = kBrowserActionWidth + kBrowserActionButtonPadding;
      int extraWidth = kContainerPadding + kChevronWidth;
      toolbarModel_->SetVisibleIconCount(
          (predefinedWidth - extraWidth) / iconWidth);
    }
  }

  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:(Extension*)extension {
  if (!extension->browser_action())
    return NSZeroPoint;
  BrowserActionButton* button = [self buttonForExtension:extension];
  if (!button)
    return NSZeroPoint;

  NSView* view = button;
  BOOL isHidden = [hiddenButtons_ containsObject:button];
  if (isHidden)
    view = chevronMenuButton_.get();

  NSPoint arrowPoint = [view frame].origin;
  // Adjust the anchor point to be at the center of the browser action button
  // or chevron.
  arrowPoint.x += NSWidth([view frame]) / 2;
  // Move the arrow up a bit in the case that it's pointing to the chevron.
  if (isHidden)
    arrowPoint.y += NSHeight([view frame]) / 4;

  return [[view superview] convertPoint:arrowPoint 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)registerUserPrefs:(PrefService*)prefs {
  prefs->RegisterRealPref(prefs::kBrowserActionContainerWidth, 0);
}

#pragma mark -
#pragma mark Private Methods

- (void)createButtons {
  if (!toolbarModel_)
    return;

  NSUInteger i = 0;
  for (ExtensionList::iterator iter = toolbarModel_->begin();
       iter != toolbarModel_->end(); ++iter) {
    if (![self shouldDisplayBrowserAction:*iter])
      continue;

    [self createActionButtonForExtension:*iter withIndex:i++];
  }

  [[NSNotificationCenter defaultCenter]
      addObserver:self
         selector:@selector(actionButtonUpdated:)
             name:kBrowserActionButtonUpdatedNotification
           object:nil];

  CGFloat width = [self savedWidth];
  [containerView_ resizeToWidth:width animate:NO];
}

- (void)createActionButtonForExtension:(Extension*)extension
                             withIndex:(NSUInteger)index {
  if (!extension->browser_action())
    return;

  if (![self shouldDisplayBrowserAction:extension])
    return;

  if (profile_->IsOffTheRecord())
    index = toolbarModel_->OriginalIndexToIncognito(index);

  // Show the container if it's the first button. Otherwise it will be shown
  // already.
  if ([self buttonCount] == 0)
    [containerView_ setHidden:NO];

  BrowserActionButton* newButton = [[[BrowserActionButton alloc]
      initWithExtension:extension
                profile:profile_
                  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];
  if (index < [self containerButtonCapacity]) {
    [containerView_ addSubview:newButton];
  } else {
    [hiddenButtons_ addObject:newButton];
    [newButton setAlphaValue:0.0];
    [self updateOverflowMenu];
  }

  [[NSNotificationCenter defaultCenter]
      addObserver:self
         selector:@selector(actionButtonDragging:)
             name:kBrowserActionButtonDraggingNotification
           object:newButton];

  [self repositionActionButtonsAndAnimate:NO];
  [containerView_ setMaxWidth:
      [self containerWidthWithButtonCount:[self buttonCount]]];
  [containerView_ setNeedsDisplay:YES];
}

- (void)removeActionButtonForExtension:(Extension*)extension {
  if (!extension->browser_action())
    return;

  NSString* buttonKey = base::SysUTF8ToNSString(extension->id());
  if (!buttonKey)
    return;

  BrowserActionButton* button = [buttons_ objectForKey:buttonKey];
  // This could be the case in incognito, where only a subset of extensions are
  // shown.
  if (!button)
    return;

  [button removeFromSuperview];
  // It may or may not be hidden, but it won't matter to NSMutableArray either
  // way.
  [hiddenButtons_ removeObject:button];
  [self updateOverflowMenu];

  [buttons_ removeObjectForKey:buttonKey];
  if ([self buttonCount] == 0) {
    // No more buttons? Hide the container.
    [containerView_ setHidden:YES];
  } else {
    [self repositionActionButtonsAndAnimate:NO];
  }
  [containerView_ setMaxWidth:
      [self containerWidthWithButtonCount:[self buttonCount]]];
  [containerView_ setNeedsDisplay:YES];
}

- (void)repositionActionButtonsAndAnimate:(BOOL)animate {
  NSUInteger i = 0;
  for (ExtensionList::iterator iter = toolbarModel_->begin();
       iter != toolbarModel_->end(); ++iter) {
    if (![self shouldDisplayBrowserAction:*iter])
      continue;
    BrowserActionButton* button = [self buttonForExtension:(*iter)];
    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];
    buttonFrame.origin.x += kButtonOpacityLeadPadding;
    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(0.0f,
        (intersectionWidth - kButtonOpacityLeadPadding) / NSWidth(buttonFrame));
    [button setAlphaValue:alpha];
    [button setNeedsDisplay:YES];
  }
}

- (BrowserActionButton*)buttonForExtension:(Extension*)extension {
  NSString* extensionId = base::SysUTF8ToNSString(extension->id());
  DCHECK(extensionId);
  if (!extensionId)
    return nil;
  return [buttons_ objectForKey:extensionId];
}

- (CGFloat)containerWidthWithButtonCount:(NSUInteger)buttonCount {
  CGFloat width = 0.0;
  if (buttonCount > 0) {
    width = kGrippyXOffset + kContainerPadding +
        (buttonCount * (kBrowserActionWidth + kBrowserActionButtonPadding));
  }
  // Make room for the chevron if any buttons are hidden.
  if ([self buttonCount] != [self visibleButtonCount]) {
    width += kChevronWidth + kBrowserActionButtonPadding;
    // Add extra padding if all buttons are hidden.
    if ([self visibleButtonCount] == 0)
      width += 3 * kBrowserActionButtonPadding;
  }
  return width;
}

- (NSUInteger)containerButtonCapacity {
  CGFloat containerWidth = [self savedWidth];
  return (containerWidth - kGrippyXOffset + kContainerPadding) /
      (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) {
    [containerView_ addSubview:[hiddenButtons_ objectAtIndex:0]];
    [hiddenButtons_ removeObjectAtIndex:0];
  }
}

- (void)containerDragging:(NSNotification*)notification {
  [[NSNotificationCenter defaultCenter]
      postNotificationName:kBrowserActionGrippyDraggingNotification
                    object:self];
}

- (void)containerDragFinished:(NSNotification*)notification {
  for (ExtensionList::iterator iter = toolbarModel_->begin();
       iter != toolbarModel_->end(); ++iter) {
    BrowserActionButton* button = [self buttonForExtension:(*iter)];
    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 updateOverflowMenu];
  [self updateGrippyCursors];

  if (!profile_->IsOffTheRecord())
    toolbarModel_->SetVisibleIconCount([self visibleButtonCount]);

  [[NSNotificationCenter defaultCenter]
      postNotificationName:kBrowserActionGrippyDragFinishedNotification
                    object:self];
}

- (void)actionButtonUpdated:(NSNotification*)notification {
  BrowserActionButton* button = [notification object];
  if (![hiddenButtons_ containsObject:button])
    return;

  // +1 item because of the title placeholder. See |updateOverflowMenu|.
  NSUInteger menuIndex = [hiddenButtons_ indexOfObject:button] + 1;
  NSMenuItem* item = [[chevronMenuButton_ attachedMenu] itemAtIndex:menuIndex];
  DCHECK(button == [item representedObject]);
  [item setImage:[button compositedImage]];
}

- (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::iterator iter = toolbarModel_->begin();
       iter != toolbarModel_->end(); ++iter) {
    BrowserActionButton* button = [self buttonForExtension:(*iter)];
    CGFloat intersectionWidth =
        NSWidth(NSIntersectionRect(draggedButtonFrame, [button frame]));

    if (intersectionWidth > dragThreshold && button != draggedButton &&
        ![button isAnimating] && index < [self visibleButtonCount]) {
      toolbarModel_->MoveBrowserAction([draggedButton extension], index);
      [self repositionActionButtonsAndAnimate:YES];
      return;
    }
    ++index;
  }
}

- (void)actionButtonDragFinished:(NSNotification*)notification {
  [self showChevronIfNecessaryInFrame:[containerView_ frame] animate:YES];
  [self repositionActionButtonsAndAnimate:YES];
}

- (void)moveButton:(BrowserActionButton*)button
           toIndex:(NSUInteger)index
           animate:(BOOL)animate {
  CGFloat xOffset = kGrippyXOffset +
      (index * (kBrowserActionWidth + kBrowserActionButtonPadding));
  NSRect buttonFrame = [button frame];
  buttonFrame.origin.x = xOffset;
  [button setFrame:buttonFrame animate:animate];
}

- (void)browserActionClicked:(BrowserActionButton*)button {
  int tabId = [self currentTabId];
  if (tabId < 0) {
    NOTREACHED() << "No current tab.";
    return;
  }

  ExtensionAction* action = [button extension]->browser_action();
  if (action->HasPopup(tabId)) {
    GURL popupUrl = action->GetPopupUrl(tabId);
    // If a popup is already showing, check if the popup URL is the same. If so,
    // then close the popup.
    ExtensionPopupController* popup = [ExtensionPopupController popup];
    if (popup &&
        [[popup window] isVisible] &&
        [popup extensionHost]->GetURL() == popupUrl) {
      [popup close];
      return;
    }
    NSPoint arrowPoint = [self popupPointForBrowserAction:[button extension]];
    [ExtensionPopupController showURL:popupUrl
                            inBrowser:browser_
                           anchoredAt:arrowPoint
                        arrowLocation:kTopRight
                              devMode:NO];
  } else {
    ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted(
       profile_, action->extension_id(), browser_);
  }
}

- (BOOL)shouldDisplayBrowserAction:(Extension*)extension {
  // Only display incognito-enabled extensions while in incognito mode.
  return (!profile_->IsOffTheRecord() ||
          profile_->GetExtensionsService()->IsIncognitoEnabled(extension));
}

- (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 - kChevronRightPadding;
  NSRect buttonFrame = NSMakeRect(xPos,
                                  kChevronLowerPadding,
                                  kChevronWidth,
                                  kChevronHeight);
  [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_ setBordered:NO];
    ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    [chevronMenuButton_ setImage:rb.GetNSImageNamed(IDR_BOOKMARK_BAR_CHEVRONS)];
    [containerView_ addSubview:chevronMenuButton_];
  }

  if (!hidden)
    [self updateOverflowMenu];

  [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)updateOverflowMenu {
  overflowMenu_.reset([[NSMenu alloc] initWithTitle:@""]);
  // See menu_button.h for documentation on why this is needed.
  [overflowMenu_ addItemWithTitle:@"" action:nil keyEquivalent:@""];

  for (BrowserActionButton* button in hiddenButtons_.get()) {
    NSString* name = base::SysUTF8ToNSString([button extension]->name());
    NSMenuItem* item =
        [overflowMenu_ addItemWithTitle:name
                                 action:@selector(chevronItemSelected:)
                          keyEquivalent:@""];
    [item setRepresentedObject:button];
    [item setImage:[button compositedImage]];
    [item setTarget:self];
  }
  [chevronMenuButton_ setAttachedMenu:overflowMenu_];
}

- (void)updateGrippyCursors {
  [containerView_ setCanDragLeft:[hiddenButtons_ count] > 0];
  [containerView_ setCanDragRight:[self visibleButtonCount] > 0];
  [[containerView_ window] invalidateCursorRectsForView:containerView_];
}

- (int)currentTabId {
  TabContents* selected_tab = browser_->GetSelectedTabContents();
  if (!selected_tab)
    return -1;

  return selected_tab->controller().session_id().id();
}

#pragma mark -
#pragma mark Testing Methods

- (NSButton*)buttonWithIndex:(NSUInteger)index {
  NSUInteger i = 0;
  for (ExtensionList::iterator iter = toolbarModel_->begin();
       iter != toolbarModel_->end(); ++iter) {
    if (i == index)
      return [buttons_ objectForKey:base::SysUTF8ToNSString((*iter)->id())];

    ++i;
  }
  return nil;
}

@end

Generated by  Doxygen 1.6.0   Back to index