diff options
author | andybons@chromium.org <andybons@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-12 18:53:09 +0000 |
---|---|---|
committer | andybons@chromium.org <andybons@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-11-12 18:53:09 +0000 |
commit | 5a2fb0052ba3649072a6a943474032e0f750c136 (patch) | |
tree | c517c33debaba26acb1ef1fc1e839dafa9a2f316 /chrome/browser | |
parent | 8761dfd26bce81b5e438cbee1f020755cabaef7c (diff) | |
download | chromium_src-5a2fb0052ba3649072a6a943474032e0f750c136.zip chromium_src-5a2fb0052ba3649072a6a943474032e0f750c136.tar.gz chromium_src-5a2fb0052ba3649072a6a943474032e0f750c136.tar.bz2 |
Initial change for the implementation of browser actions on the mac.
Popups are not implemented within this change.
BUG=23881
TEST=Install a browser action extension on the mac. Observe that something actually happens in the UI.
Review URL: http://codereview.chromium.org/366029
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@31803 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
5 files changed, 579 insertions, 12 deletions
diff --git a/chrome/browser/cocoa/extensions/browser_actions_controller.h b/chrome/browser/cocoa/extensions/browser_actions_controller.h new file mode 100644 index 0000000..eb026c1 --- /dev/null +++ b/chrome/browser/cocoa/extensions/browser_actions_controller.h @@ -0,0 +1,76 @@ +// Copyright (c) 2009 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. + +#ifndef CHROME_BROWSER_COCOA_EXTENSIONS_BROWSER_ACTIONS_CONTROLLER_H_ +#define CHROME_BROWSER_COCOA_EXTENSIONS_BROWSER_ACTIONS_CONTROLLER_H_ + +#import <Cocoa/Cocoa.h> + +#import "base/scoped_nsobject.h" +#include "base/scoped_ptr.h" + +class Browser; +@class BrowserActionButton; +class Extension; +class ExtensionsServiceObserverBridge; +class Profile; + +extern const CGFloat kBrowserActionButtonPadding; +extern const CGFloat kBrowserActionWidth; + +extern NSString* const kBrowserActionsChangedNotification; + +@interface BrowserActionsController : NSObject { + @private + // Reference to the current browser. Weak. + Browser* browser_; + + // The view from Toolbar.xib we'll be rendering our browser actions in. Weak. + NSView* containerView_; + + // The current profile. Weak. + Profile* profile_; + + // The observer for the ExtensionsService we're getting events from. + scoped_ptr<ExtensionsServiceObserverBridge> observer_; + + // A dictionary of Extension ID -> BrowserActionButton pairs representing the + // buttons present in the container view. The ID is a string unique to each + // extension. + scoped_nsobject<NSMutableDictionary> buttons_; + + // The order of the BrowserActionButton objects within the dictionary. + scoped_nsobject<NSMutableArray> buttonOrder_; +} + +// Initializes the controller given the current browser and container view that +// will hold the browser action buttons. +- (id)initWithBrowser:(Browser*)browser + containerView:(NSView*)container; + +// Creates and appends any existing browser action buttons present within the +// extensions service to the toolbar. +- (void)createButtons; + +// Hides the browser action's popup menu (if one is present and visible). +- (void)hidePopup; + +// Marks the container view for redraw. Called by the extension service +// notification bridge. +- (void)browserActionVisibilityHasChanged; + +// Returns the current number of browser action buttons displayed in the +// container. +- (int)buttonCount; + +// Executes the action designated by the extension. +- (void)browserActionClicked:(BrowserActionButton*)sender; + +// Returns the current ID of the active tab, -1 in the case where the user is in +// incognito mode. +- (int)currentTabId; + +@end // @interface BrowserActionsController + +#endif // CHROME_BROWSER_COCOA_EXTENSIONS_BROWSER_ACTIONS_CONTROLLER_H_ diff --git a/chrome/browser/cocoa/extensions/browser_actions_controller.mm b/chrome/browser/cocoa/extensions/browser_actions_controller.mm new file mode 100644 index 0000000..bf7707f --- /dev/null +++ b/chrome/browser/cocoa/extensions/browser_actions_controller.mm @@ -0,0 +1,421 @@ +// Copyright (c) 2009 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 <string> + +#include "app/gfx/canvas_paint.h" +#include "base/sys_string_conversions.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/cocoa/toolbar_button_cell.h" +#include "chrome/browser/extensions/extension_browser_event_router.h" +#include "chrome/browser/extensions/extensions_service.h" +#include "chrome/browser/extensions/extension_tabs_module.h" +#include "chrome/browser/extensions/image_loading_tracker.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 "skia/ext/skia_utils_mac.h" + +static const CGFloat kBrowserActionBadgeOriginYOffset = -4; + +// 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. +static const CGFloat kBrowserActionOriginYOffset = 5; + +// The size of each button on the toolbar. +static const CGFloat kBrowserActionHeight = 27; +extern const CGFloat kBrowserActionWidth = 29; + +// The padding between browser action buttons. +extern const CGFloat kBrowserActionButtonPadding = 3; + +NSString* const kBrowserActionsChangedNotification = @"BrowserActionsChanged"; + +@interface BrowserActionBadgeView : NSView { + @private + // The current tab ID used when drawing the badge. + int tabId_; + + // The action we're drawing the badge for. Weak. + ExtensionAction* extensionAction_; +} + +@property(readwrite, nonatomic) int tabId; +@property(readwrite, nonatomic) ExtensionAction* extensionAction; + +@end + +@implementation BrowserActionBadgeView + +- (void)drawRect:(NSRect)dirtyRect { + // CanvasPaint draws its content to the current NSGraphicsContext in its + // destructor. If anything needs to be drawn afterwards, then enclose this + // in a nested block. + NSRect badgeBounds = [self bounds]; + badgeBounds.origin.y += kBrowserActionBadgeOriginYOffset; + gfx::CanvasPaint canvas(badgeBounds, false); + canvas.set_composite_alpha(true); + gfx::Rect boundingRect(NSRectToCGRect(badgeBounds)); + extensionAction_->PaintBadge(&canvas, boundingRect, tabId_); +} + +@synthesize tabId = tabId_; +@synthesize extensionAction = extensionAction_; + +@end + +class ExtensionImageTrackerBridge; + +@interface BrowserActionButton : NSButton { + @private + scoped_ptr<ExtensionImageTrackerBridge> imageLoadingBridge_; + + scoped_nsobject<NSImage> defaultIcon_; + + scoped_nsobject<NSImage> tabSpecificIcon_; + + scoped_nsobject<NSView> badgeView_; + + // The extension for this button. Weak. + Extension* extension_; + + // Weak. Owns us. + BrowserActionsController* controller_; +} + +- (id)initWithExtension:(Extension*)extension + controller:(BrowserActionsController*)controller + xOffset:(int)xOffset; + +- (void)setDefaultIcon:(NSImage*)image; + +- (void)setTabSpecificIcon:(NSImage*)image; + +- (void)updateState; + +@property(readonly, nonatomic) Extension* extension; + +@end + +// A helper class to bridge the asynchronous Skia bitmap loading mechanism to +// the extension's button. +class ExtensionImageTrackerBridge : public NotificationObserver, + public ImageLoadingTracker::Observer { + public: + ExtensionImageTrackerBridge(BrowserActionButton* owner, Extension* extension) + : owner_(owner), + tracker_(NULL) { + // The Browser Action API does not allow the default icon path to be + // changed at runtime, so we can load this now and cache it. + std::string path = extension->browser_action()->default_icon_path(); + if (!path.empty()) { + tracker_ = new ImageLoadingTracker(this, 1); + tracker_->PostLoadImageTask(extension->GetResource(path), + gfx::Size(Extension::kBrowserActionIconMaxSize, + Extension::kBrowserActionIconMaxSize)); + } + registrar_.Add(this, NotificationType::EXTENSION_BROWSER_ACTION_UPDATED, + Source<ExtensionAction>(extension->browser_action())); + } + + ~ExtensionImageTrackerBridge() { + if (tracker_) + tracker_->StopTrackingImageLoad(); + } + + // ImageLoadingTracker::Observer implementation. + void OnImageLoaded(SkBitmap* image, size_t index) { + if (image) + [owner_ setDefaultIcon:gfx::SkBitmapToNSImage(*image)]; + tracker_ = NULL; + [owner_ updateState]; + } + + // Overridden from NotificationObserver. + void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NotificationType::EXTENSION_BROWSER_ACTION_UPDATED) + [owner_ updateState]; + else + NOTREACHED(); + } + + private: + // Weak. Owns us. + BrowserActionButton* owner_; + + // Loads the button's icons for us on the file thread. Weak. + ImageLoadingTracker* tracker_; + + // Used for registering to receive notifications and automatic clean up. + NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionImageTrackerBridge); +}; + +@implementation BrowserActionButton + +- (id)initWithExtension:(Extension*)extension + controller:(BrowserActionsController*)controller + xOffset:(int)xOffset { + NSRect frame = NSMakeRect(xOffset, + kBrowserActionOriginYOffset, + kBrowserActionWidth, + kBrowserActionHeight); + if ((self = [super initWithFrame:frame])) { + ToolbarButtonCell* cell = [[[ToolbarButtonCell alloc] init] autorelease]; + // [NSButton setCell:] warns to NOT use setCell: other than in the + // initializer of a control. However, we are using a basic + // NSButton whose initializer does not take an NSCell as an + // object. To honor the assumed semantics, we do nothing with + // NSButton between alloc/init and setCell:. + [self setCell:cell]; + [self setTitle:@""]; + [self setButtonType:NSMomentaryChangeButton]; + [self setShowsBorderOnlyWhileMouseInside:YES]; + + [self setTarget:controller]; + [self setAction:@selector(browserActionClicked:)]; + + extension_ = extension; + controller_ = controller; + imageLoadingBridge_.reset(new ExtensionImageTrackerBridge(self, extension)); + + NSRect badgeFrame = [self bounds]; + badgeView_.reset([[BrowserActionBadgeView alloc] initWithFrame:badgeFrame]); + [badgeView_ setTabId:[controller currentTabId]]; + [badgeView_ setExtensionAction:extension->browser_action()]; + [self addSubview:badgeView_]; + + [self updateState]; + } + + return self; +} + +- (void)setDefaultIcon:(NSImage*)image { + defaultIcon_.reset([image retain]); +} + +- (void)setTabSpecificIcon:(NSImage*)image { + tabSpecificIcon_.reset([image retain]); +} + +- (void)updateState { + int tabId = [controller_ currentTabId]; + if (tabId < 0) + return; + + std::string tooltip = extension_->browser_action()->GetTitle(tabId); + if (!tooltip.empty()) + [self setToolTip:base::SysUTF8ToNSString(tooltip)]; + + SkBitmap image = extension_->browser_action()->GetIcon(tabId); + if (!image.isNull()) { + [self setTabSpecificIcon:gfx::SkBitmapToNSImage(image)]; + [self setImage:tabSpecificIcon_]; + } else if (defaultIcon_) { + [self setImage:defaultIcon_]; + } + + [badgeView_ setTabId:tabId]; + + [self setNeedsDisplay:YES]; +} + +@synthesize extension = extension_; + +@end + +@interface BrowserActionsController(Private) + +- (void)createActionButtonForExtension:(Extension*)extension; +- (void)removeActionButtonForExtension:(Extension*)extension; +- (void)repositionActionButtons; + +@end + +// A helper class to proxy extension notifications to the view controller's +// appropriate methods. +class ExtensionsServiceObserverBridge : public NotificationObserver { + public: + ExtensionsServiceObserverBridge(BrowserActionsController* owner, + Profile* profile) : owner_(owner) { + registrar_.Add(this, NotificationType::EXTENSION_LOADED, + Source<Profile>(profile)); + registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, + Source<Profile>(profile)); + registrar_.Add(this, NotificationType::EXTENSION_UNLOADED_DISABLED, + Source<Profile>(profile)); + registrar_.Add(this, NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE, + Source<Profile>(profile)); + } + + // Runs |owner_|'s method corresponding to the event type received from the + // notification system. + // Overridden from NotificationObserver. + void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + switch (type.value) { + case NotificationType::EXTENSION_LOADED: { + Extension* extension = Details<Extension>(details).ptr(); + [owner_ createActionButtonForExtension:extension]; + [owner_ browserActionVisibilityHasChanged]; + break; + } + case NotificationType::EXTENSION_UNLOADED: + case NotificationType::EXTENSION_UNLOADED_DISABLED: { + Extension* extension = Details<Extension>(details).ptr(); + [owner_ removeActionButtonForExtension:extension]; + [owner_ browserActionVisibilityHasChanged]; + break; + } + case NotificationType::EXTENSION_HOST_VIEW_SHOULD_CLOSE: + //if (Details<ExtensionHost>(popup_->host()) != details) + // return; + [owner_ hidePopup]; + break; + default: + NOTREACHED() << L"Unexpected notification"; + } + } + + 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 + +- (id)initWithBrowser:(Browser*)browser + containerView:(NSView*)container { + DCHECK(browser && container); + + if ((self = [super init])) { + browser_ = browser; + profile_ = browser->profile(); + + containerView_ = container; + [containerView_ setHidden:YES]; + observer_.reset(new ExtensionsServiceObserverBridge(self, profile_)); + buttons_.reset([[NSMutableDictionary alloc] init]); + buttonOrder_.reset([[NSMutableArray alloc] init]); + } + + return self; +} + +- (void)hidePopup { + NOTIMPLEMENTED(); +} + +- (void)browserActionVisibilityHasChanged { + [containerView_ setNeedsDisplay:YES]; +} + +- (void)createButtons { + ExtensionsService* extensionsService = profile_->GetExtensionsService(); + if (!extensionsService) // |extensionsService| can be NULL in Incognito. + return; + + for (size_t i = 0; i < extensionsService->extensions()->size(); ++i) { + Extension* extension = extensionsService->GetExtensionById( + extensionsService->extensions()->at(i)->id(), false); + if (extension->browser_action()) { + [self createActionButtonForExtension:extension]; + } + } +} + +- (void)createActionButtonForExtension:(Extension*)extension { + if (!extension->browser_action()) + return; + + if ([buttons_ count] == 0) { + // Only call if we're adding our first button, otherwise it will be shown + // already. + [containerView_ setHidden:NO]; + } + + int xOffset = + [buttons_ count] * (kBrowserActionWidth + kBrowserActionButtonPadding); + BrowserActionButton* newButton = + [[[BrowserActionButton alloc] initWithExtension:extension + controller:self + xOffset:xOffset] autorelease]; + NSString* buttonKey = base::SysUTF8ToNSString(extension->id()); + [buttons_ setObject:newButton forKey:buttonKey]; + [buttonOrder_ addObject:newButton]; + [containerView_ addSubview:newButton]; + + [[NSNotificationCenter defaultCenter] + postNotificationName:kBrowserActionsChangedNotification object:self]; +} + +- (void)removeActionButtonForExtension:(Extension*)extension { + NSString* buttonKey = base::SysUTF8ToNSString(extension->id()); + + BrowserActionButton* button = [buttons_ objectForKey:buttonKey]; + [button removeFromSuperview]; + [buttons_ removeObjectForKey:buttonKey]; + [buttonOrder_ removeObject:button]; + if ([buttons_ count] == 0) { + // No more buttons? Hide the container. + [containerView_ setHidden:YES]; + } else { + // repositionActionButtons only needs to be called if removing a browser + // action button because adding one will always append to the end of the + // container, while removing one may require that those to the right of it + // be shifted to the left. + [self repositionActionButtons]; + } + [[NSNotificationCenter defaultCenter] + postNotificationName:kBrowserActionsChangedNotification object:self]; +} + +- (void)repositionActionButtons { + for (NSUInteger i = 0; i < [buttonOrder_ count]; ++i) { + CGFloat xOffset = i * (kBrowserActionWidth + kBrowserActionButtonPadding); + BrowserActionButton* button = [buttonOrder_ objectAtIndex:i]; + NSRect buttonFrame = [button frame]; + buttonFrame.origin.x = xOffset; + [button setFrame:buttonFrame]; + } +} + +- (int)buttonCount { + return [buttons_ count]; +} + +- (void)browserActionClicked:(BrowserActionButton*)sender { + ExtensionAction* action = [sender extension]->browser_action(); + if (action->has_popup()) { + // Popups are not implemented for Mac yet. + NOTIMPLEMENTED(); + } else { + ExtensionBrowserEventRouter::GetInstance()->BrowserActionExecuted( + profile_, action->extension_id(), browser_); + } +} + +- (int)currentTabId { + TabContents* selected_tab = browser_->GetSelectedTabContents(); + if (!selected_tab) + return -1; + + return selected_tab->controller().session_id().id(); +} + +@end diff --git a/chrome/browser/cocoa/toolbar_controller.h b/chrome/browser/cocoa/toolbar_controller.h index 6af4241..991ff39 100644 --- a/chrome/browser/cocoa/toolbar_controller.h +++ b/chrome/browser/cocoa/toolbar_controller.h @@ -20,6 +20,7 @@ @class BackForwardMenuController; @class BackgroundGradientView; class Browser; +@class BrowserActionsController; class BubblePositioner; class CommandUpdater; @class DelayedMenuButton; @@ -52,6 +53,7 @@ class ToolbarView; id<ViewResizer> resizeDelegate_; // weak scoped_nsobject<BackForwardMenuController> backMenuController_; scoped_nsobject<BackForwardMenuController> forwardMenuController_; + scoped_nsobject<BrowserActionsController> browserActionsController_; // Used for monitoring the optional toolbar button prefs. scoped_ptr<ToolbarControllerInternal::PrefObserverBridge> prefObserver_; @@ -88,6 +90,7 @@ class ToolbarView; IBOutlet MenuButton* wrenchButton_; IBOutlet AutocompleteTextField* locationBar_; IBOutlet NSMenu* encodingMenu_; + IBOutlet NSView* browserActionContainerView_; } // Initialize the toolbar and register for command updates. The profile is diff --git a/chrome/browser/cocoa/toolbar_controller.mm b/chrome/browser/cocoa/toolbar_controller.mm index ac896f8..9092b51 100644 --- a/chrome/browser/cocoa/toolbar_controller.mm +++ b/chrome/browser/cocoa/toolbar_controller.mm @@ -16,6 +16,7 @@ #import "chrome/browser/cocoa/autocomplete_text_field_editor.h" #import "chrome/browser/cocoa/back_forward_menu_controller.h" #import "chrome/browser/cocoa/encoding_menu_controller_delegate_mac.h" +#import "chrome/browser/cocoa/extensions/browser_actions_controller.h" #import "chrome/browser/cocoa/gradient_button_cell.h" #import "chrome/browser/cocoa/location_bar_view_mac.h" #import "chrome/browser/cocoa/menu_button.h" @@ -50,6 +51,8 @@ static const float kBookmarkBarOverlap = 6.0; @interface ToolbarController(Private) - (void)initCommandStatus:(CommandUpdater*)commands; - (void)prefChanged:(std::wstring*)prefName; +- (void)browserActionsChanged; +- (void)adjustLocationAndGoPositionsBy:(CGFloat)dX; @end namespace { @@ -126,6 +129,8 @@ class PrefObserverBridge : public NotificationObserver { // the "parent" view continues to work. hasToolbar_ = YES; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + if (trackingArea_.get()) [[self view] removeTrackingArea:trackingArea_.get()]; [super dealloc]; @@ -178,7 +183,18 @@ class PrefObserverBridge : public NotificationObserver { initWithBrowser:browser_ modelType:BACK_FORWARD_MENU_TYPE_FORWARD button:forwardButton_]); - + browserActionsController_.reset([[BrowserActionsController alloc] + initWithBrowser:browser_ + containerView:browserActionContainerView_]); + // When new browser actions are added/removed, the container view for them is + // resized, necessitating the probable resizing of surrounding elements + // handled by this controller. + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(browserActionsChanged) + name:kBrowserActionsChangedNotification + object:browserActionsController_]; + [browserActionsController_ createButtons]; // For a popup window, the toolbar is really just a location bar // (see override for [ToolbarController view], below). When going // fullscreen, we remove the toolbar controller's view from the view @@ -362,13 +378,13 @@ class PrefObserverBridge : public NotificationObserver { - (NSArray*)toolbarViews { return [NSArray arrayWithObjects:backButton_, forwardButton_, reloadButton_, homeButton_, starButton_, goButton_, pageButton_, wrenchButton_, - locationBar_, encodingMenu_, nil]; + locationBar_, encodingMenu_, browserActionContainerView_, nil]; } // Moves |rect| to the right by |delta|, keeping the right side fixed by // shrinking the width to compensate. Passing a negative value for |deltaX| // moves to the left and increases the width. -- (NSRect)adjustRect:(NSRect)rect byAmount:(float)deltaX { +- (NSRect)adjustRect:(NSRect)rect byAmount:(CGFloat)deltaX { NSRect frame = NSOffsetRect(rect, deltaX, 0); frame.size.width -= deltaX; return frame; @@ -377,7 +393,7 @@ class PrefObserverBridge : public NotificationObserver { // Computes the padding between the buttons that should have a separation from // the positions in the nib. Since the forward and reload buttons are always // visible, we use those buttons as the canonical spacing. -- (float)interButtonSpacing { +- (CGFloat)interButtonSpacing { NSRect forwardFrame = [forwardButton_ frame]; NSRect reloadFrame = [reloadButton_ frame]; DCHECK(NSMinX(reloadFrame) > NSMaxX(forwardFrame)); @@ -396,7 +412,7 @@ class PrefObserverBridge : public NotificationObserver { // Always shift the star and text field by the width of the home button plus // the appropriate gap width. If we're hiding the button, we have to // reverse the direction of the movement (to the left). - float moveX = [self interButtonSpacing] + [homeButton_ frame].size.width; + CGFloat moveX = [self interButtonSpacing] + [homeButton_ frame].size.width; if (hide) moveX *= -1; // Reverse the direction of the move. @@ -421,14 +437,20 @@ class PrefObserverBridge : public NotificationObserver { // buttons, we have to reverse the direction of movement (to the left). Unlike // the home button above, we only ever have to resize the text field, we don't // have to move it. - float moveX = 2 * [self interButtonSpacing] + NSWidth([pageButton_ frame]) + - NSWidth([wrenchButton_ frame]); + CGFloat moveX = 2 * [self interButtonSpacing] + NSWidth([pageButton_ frame]) + + NSWidth([wrenchButton_ frame]); + + // Adjust for the extra unit of inter-button spacing added when the page and + // wrench buttons are hidden. + if ([browserActionsController_ buttonCount] > 0) + moveX -= [self interButtonSpacing]; + if (!hide) moveX *= -1; // Reverse the direction of the move. - [goButton_ setFrame:NSOffsetRect([goButton_ frame], moveX, 0)]; - NSRect locationFrame = [locationBar_ frame]; - locationFrame.size.width += moveX; - [locationBar_ setFrame:locationFrame]; + + [self adjustLocationAndGoPositionsBy:moveX]; + [browserActionContainerView_ setFrame:NSOffsetRect( + [browserActionContainerView_ frame], moveX, 0)]; [pageButton_ setHidden:hide]; [wrenchButton_ setHidden:hide]; @@ -443,6 +465,50 @@ class PrefObserverBridge : public NotificationObserver { } } +- (void)browserActionsChanged { + // Calculate the new width. + int buttonCount = [browserActionsController_ buttonCount]; + + CGFloat width = 0.0; + if (buttonCount > 0) { + width = (buttonCount * + (kBrowserActionWidth + kBrowserActionButtonPadding)) - + kBrowserActionButtonPadding; // No padding after last button. + } + + NSRect containerFrame = [browserActionContainerView_ frame]; + CGFloat buttonSpacing = [self interButtonSpacing]; + CGFloat dX = containerFrame.size.width - width; + containerFrame.size.width = width; + + bool addingButton = (dX < 0); + // If a button is being added, add spacing inward by negating the value. + if (addingButton) + buttonSpacing *= -1; + + // If the first button is being added or the last button is being removed, + // then account for the right padding it will need. + if ((buttonCount == 1 && addingButton) || + (buttonCount == 0 && !addingButton)) { + dX += buttonSpacing; + // The offset of the buttons from the right side will be one button spacing + // unit more than if the wrench and page buttons were shown. + if ([pageButton_ isHidden] && [wrenchButton_ isHidden]) { + dX += buttonSpacing; + } + } + + [browserActionContainerView_ setFrame:NSOffsetRect(containerFrame, dX, 0)]; + [self adjustLocationAndGoPositionsBy:dX]; +} + +- (void)adjustLocationAndGoPositionsBy:(CGFloat)dX { + [goButton_ setFrame:NSOffsetRect([goButton_ frame], dX, 0)]; + NSRect locationFrame = [locationBar_ frame]; + locationFrame.size.width += dX; + [locationBar_ setFrame:locationFrame]; +} + - (NSRect)starButtonInWindowCoordinates { return [[[starButton_ window] contentView] convertRect:[starButton_ bounds] fromView:starButton_]; diff --git a/chrome/browser/cocoa/toolbar_controller_unittest.mm b/chrome/browser/cocoa/toolbar_controller_unittest.mm index 4b96644..5c731fd 100644 --- a/chrome/browser/cocoa/toolbar_controller_unittest.mm +++ b/chrome/browser/cocoa/toolbar_controller_unittest.mm @@ -44,7 +44,8 @@ class ToolbarControllerTest : public PlatformTest { // |-toolbarViews| method. enum { kBackIndex, kForwardIndex, kReloadIndex, kHomeIndex, kStarIndex, kGoIndex, - kPageIndex, kWrenchIndex, kLocationIndex, kEncodingMenuIndex + kPageIndex, kWrenchIndex, kLocationIndex, kEncodingMenuIndex, + kBrowserActionContainerViewIndex }; ToolbarControllerTest() { |