Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1224)

Unified Diff: chrome/browser/cocoa/extensions/browser_actions_controller.mm

Issue 366029: Initial change for the implementation of browser actions on the mac.... (Closed) Base URL: http://src.chromium.org/svn/trunk/src/
Patch Set: '' Created 11 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/cocoa/extensions/browser_actions_controller.mm
===================================================================
--- chrome/browser/cocoa/extensions/browser_actions_controller.mm (revision 0)
+++ chrome/browser/cocoa/extensions/browser_actions_controller.mm (revision 0)
@@ -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
Property changes on: chrome/browser/cocoa/extensions/browser_actions_controller.mm
___________________________________________________________________
Added: svn:eol-style
+ LF
« no previous file with comments | « chrome/browser/cocoa/extensions/browser_actions_controller.h ('k') | chrome/browser/cocoa/toolbar_controller.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698