diff options
Diffstat (limited to 'chrome/browser')
7 files changed, 320 insertions, 37 deletions
diff --git a/chrome/browser/cocoa/autocomplete_text_field.mm b/chrome/browser/cocoa/autocomplete_text_field.mm index e7fc037..0697a92 100644 --- a/chrome/browser/cocoa/autocomplete_text_field.mm +++ b/chrome/browser/cocoa/autocomplete_text_field.mm @@ -70,15 +70,16 @@ // a decoration area and get the expected selection behaviour, // likewise for multiple clicks in those areas. - (void)mouseDown:(NSEvent*)theEvent { - const NSPoint locationInWindow = [theEvent locationInWindow]; - const NSPoint location = [self convertPoint:locationInWindow fromView:nil]; + const NSPoint location = + [self convertPoint:[theEvent locationInWindow] fromView:nil]; + const NSRect bounds([self bounds]); AutocompleteTextFieldCell* cell = [self autocompleteTextFieldCell]; - const NSRect textFrame([cell textFrameForFrame:[self bounds]]); + const NSRect textFrame([cell textFrameForFrame:bounds]); // A version of the textFrame which extends across the field's // entire width. - const NSRect bounds([self bounds]); + const NSRect fullFrame(NSMakeRect(bounds.origin.x, textFrame.origin.y, bounds.size.width, textFrame.size.height)); @@ -88,8 +89,9 @@ // above/below test is needed because NSTextView treats mouse events // above/below as select-to-end-in-that-direction, which makes // things janky. - if (NSMouseInRect(location, textFrame, [self isFlipped]) || - !NSMouseInRect(location, fullFrame, [self isFlipped])) { + BOOL flipped = [self isFlipped]; + if (NSMouseInRect(location, textFrame, flipped) || + !NSMouseInRect(location, fullFrame, flipped)) { [super mouseDown:theEvent]; // After the event has been handled, if the current event is a @@ -114,19 +116,18 @@ // If the user clicked the security hint icon in the cell, display the page // info window. - const NSRect hintIconFrame = [cell securityImageFrameForFrame:[self bounds]]; - if (NSMouseInRect(location, hintIconFrame, [self isFlipped])) { + const NSRect hintIconFrame = [cell securityImageFrameForFrame:bounds]; + if (NSMouseInRect(location, hintIconFrame, flipped)) { [cell onSecurityIconMousePressed]; return; } - // If the user clicked a Page Action icon, execute its action. - const NSRect iconFrame([self bounds]); + const BOOL ctrlKey = ([theEvent modifierFlags] & NSControlKeyMask) != 0; + // If the user left-clicked a Page Action icon, execute its action. const size_t pageActionCount = [cell pageActionCount]; for (size_t i = 0; i < pageActionCount; ++i) { - NSRect pageActionFrame = [cell pageActionFrameForIndex:i inFrame:iconFrame]; - if (NSMouseInRect(location, pageActionFrame, [self isFlipped])) { - // TODO(pamg): Do we need to send the event? + NSRect pageActionFrame = [cell pageActionFrameForIndex:i inFrame:bounds]; + if (NSMouseInRect(location, pageActionFrame, flipped) && !ctrlKey) { [cell onPageActionMousePressedIn:pageActionFrame forIndex:i]; return; } diff --git a/chrome/browser/cocoa/autocomplete_text_field_cell.h b/chrome/browser/cocoa/autocomplete_text_field_cell.h index 13b0e7c..bcd595e 100644 --- a/chrome/browser/cocoa/autocomplete_text_field_cell.h +++ b/chrome/browser/cocoa/autocomplete_text_field_cell.h @@ -9,6 +9,8 @@ #include "base/scoped_nsobject.h" #include "chrome/browser/cocoa/location_bar_view_mac.h" +class ExtensionAction; + // AutocompleteTextFieldCell extends StyledTextFieldCell to provide support for // certain decorations to be applied to the field. These are the search hint // ("Type to search" on the right-hand side), the keyword hint ("Press [Tab] to @@ -76,6 +78,10 @@ // given index. - (NSString*)pageActionToolTipForIndex:(size_t)index; +// Returns a pointer to the ExtensionAction object that the view at the +// specified index represents. +- (ExtensionAction*)pageActionForIndex:(size_t)index; + // Called when the Page Action at the given index, whose icon is drawn in the // iconFrame, is visible and clicked. Passed through to the list of views to // handle the click. diff --git a/chrome/browser/cocoa/autocomplete_text_field_cell.mm b/chrome/browser/cocoa/autocomplete_text_field_cell.mm index 9df998b..8f54193 100644 --- a/chrome/browser/cocoa/autocomplete_text_field_cell.mm +++ b/chrome/browser/cocoa/autocomplete_text_field_cell.mm @@ -342,6 +342,10 @@ CGFloat WidthForKeyword(NSAttributedString* keywordString) { return page_action_views_->ViewAt(index)->GetToolTip(); } +- (ExtensionAction*)pageActionForIndex:(size_t)index { + return page_action_views_->ViewAt(index)->page_action(); +} + - (void)drawHintWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { DCHECK(hintString_); diff --git a/chrome/browser/cocoa/autocomplete_text_field_editor.mm b/chrome/browser/cocoa/autocomplete_text_field_editor.mm index 54a866e..cf6e77b 100644 --- a/chrome/browser/cocoa/autocomplete_text_field_editor.mm +++ b/chrome/browser/cocoa/autocomplete_text_field_editor.mm @@ -9,9 +9,21 @@ #include "grit/generated_resources.h" #include "base/sys_string_conversions.h" #include "chrome/app/chrome_dll_resource.h" // IDC_* +#include "chrome/browser/browser_list.h" #import "chrome/browser/cocoa/autocomplete_text_field.h" +#import "chrome/browser/cocoa/autocomplete_text_field_cell.h" #import "chrome/browser/cocoa/browser_window_controller.h" +#import "chrome/browser/cocoa/extensions/extension_action_context_menu.h" #import "chrome/browser/cocoa/toolbar_controller.h" +#include "chrome/browser/extensions/extensions_service.h" +#include "chrome/common/extensions/extension_action.h" + +class Extension; + +@interface AutocompleteTextFieldEditor(Private) +// Returns the default context menu to be displayed on a right mouse click. +- (NSMenu*)defaultMenuForEvent:(NSEvent*)event; +@end @implementation AutocompleteTextFieldEditor @@ -78,14 +90,42 @@ // NSTextField and NSTextView synchronize their contents. That is // probably unavoidable because in most cases having rich text in the // field you probably would expect it to update the font panel. -- (void)updateFontPanel { -} +- (void)updateFontPanel {} // No ruler bar, so don't update any of that state, either. -- (void)updateRuler { -} +- (void)updateRuler {} - (NSMenu*)menuForEvent:(NSEvent*)event { + NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil]; + + // Was the right click within a Page Action? Show a different menu if so. + NSRect bounds([[self delegate] bounds]); + AutocompleteTextFieldCell* cell = [[self delegate] autocompleteTextFieldCell]; + const size_t pageActionCount = [cell pageActionCount]; + BOOL flipped = [self isFlipped]; + Browser* browser = BrowserList::GetLastActive(); + // GetLastActive() returns NULL during testing. + if (!browser) + return [self defaultMenuForEvent:event]; + ExtensionsService* service = browser->profile()->GetExtensionsService(); + for (size_t i = 0; i < pageActionCount; ++i) { + NSRect pageActionFrame = [cell pageActionFrameForIndex:i inFrame:bounds]; + if (NSMouseInRect(location, pageActionFrame, flipped)) { + Extension* extension = service->GetExtensionById( + [cell pageActionForIndex:i]->extension_id(), false); + DCHECK(extension); + if (!extension) + break; + return [[[ExtensionActionContextMenu alloc] initWithExtension:extension] + autorelease]; + } + } + + // Otherwise, simply return the default menu for this instance. + return [self defaultMenuForEvent:event]; +} + +- (NSMenu*)defaultMenuForEvent:(NSEvent*)event { NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"TITLE"] autorelease]; [menu addItemWithTitle:l10n_util::GetNSStringWithFixup(IDS_CUT) action:@selector(cut:) diff --git a/chrome/browser/cocoa/extensions/browser_actions_controller.mm b/chrome/browser/cocoa/extensions/browser_actions_controller.mm index f60dd88..345ba69 100644 --- a/chrome/browser/cocoa/extensions/browser_actions_controller.mm +++ b/chrome/browser/cocoa/extensions/browser_actions_controller.mm @@ -9,6 +9,7 @@ #include "app/gfx/canvas_paint.h" #include "base/sys_string_conversions.h" #include "chrome/browser/browser.h" +#include "chrome/browser/cocoa/extensions/extension_action_context_menu.h" #include "chrome/browser/cocoa/extensions/extension_popup_controller.h" #include "chrome/browser/cocoa/toolbar_button_cell.h" #include "chrome/browser/extensions/extension_browser_event_router.h" @@ -21,7 +22,7 @@ #include "chrome/common/notification_registrar.h" #include "skia/ext/skia_utils_mac.h" -static const CGFloat kBrowserActionBadgeOriginYOffset = -4; +static const CGFloat kBrowserActionBadgeOriginYOffset = 5; // 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 @@ -37,12 +38,12 @@ extern const CGFloat kBrowserActionButtonPadding = 3; NSString* const kBrowserActionsChangedNotification = @"BrowserActionsChanged"; -@interface BrowserActionBadgeView : NSView { +@interface BrowserActionCell : ToolbarButtonCell { @private - // The current tab ID used when drawing the badge. + // The current tab ID used when drawing the cell. int tabId_; - // The action we're drawing the badge for. Weak. + // The action we're drawing the cell for. Weak. ExtensionAction* extensionAction_; } @@ -51,17 +52,18 @@ NSString* const kBrowserActionsChangedNotification = @"BrowserActionsChanged"; @end -@implementation BrowserActionBadgeView +@implementation BrowserActionCell + +- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView*)controlView { + [super drawWithFrame:cellFrame inView:controlView]; -- (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); + cellFrame.origin.y += kBrowserActionBadgeOriginYOffset; + gfx::CanvasPaint canvas(cellFrame, false); canvas.set_composite_alpha(true); - gfx::Rect boundingRect(NSRectToCGRect(badgeBounds)); + gfx::Rect boundingRect(NSRectToCGRect(cellFrame)); extensionAction_->PaintBadge(&canvas, boundingRect, tabId_); } @@ -80,8 +82,6 @@ class ExtensionImageTrackerBridge; scoped_nsobject<NSImage> tabSpecificIcon_; - scoped_nsobject<NSView> badgeView_; - // The extension for this button. Weak. Extension* extension_; @@ -162,6 +162,10 @@ class ExtensionImageTrackerBridge : public NotificationObserver, @implementation BrowserActionButton ++ (Class)cellClass { + return [BrowserActionCell class]; +} + - (id)initWithExtension:(Extension*)extension controller:(BrowserActionsController*)controller xOffset:(int)xOffset { @@ -170,13 +174,16 @@ class ExtensionImageTrackerBridge : public NotificationObserver, kBrowserActionWidth, kBrowserActionHeight); if ((self = [super initWithFrame:frame])) { - ToolbarButtonCell* cell = [[[ToolbarButtonCell alloc] init] autorelease]; + BrowserActionCell* cell = [[[BrowserActionCell 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]; + [cell setTabId:[controller currentTabId]]; + [cell setExtensionAction:extension->browser_action()]; + [self setTitle:@""]; [self setButtonType:NSMomentaryChangeButton]; [self setShowsBorderOnlyWhileMouseInside:YES]; @@ -184,16 +191,13 @@ class ExtensionImageTrackerBridge : public NotificationObserver, [self setTarget:controller]; [self setAction:@selector(browserActionClicked:)]; + [self setMenu:[[[ExtensionActionContextMenu alloc] + initWithExtension:extension] autorelease]]; + 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]; } @@ -228,7 +232,7 @@ class ExtensionImageTrackerBridge : public NotificationObserver, [self setImage:defaultIcon_]; } - [badgeView_ setTabId:tabId]; + [[self cell] setTabId:tabId]; [self setNeedsDisplay:YES]; } diff --git a/chrome/browser/cocoa/extensions/extension_action_context_menu.h b/chrome/browser/cocoa/extensions/extension_action_context_menu.h new file mode 100644 index 0000000..ffae3ed --- /dev/null +++ b/chrome/browser/cocoa/extensions/extension_action_context_menu.h @@ -0,0 +1,33 @@ +// 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. + +#ifndef CHROME_BROWSER_COCOA_EXTENSIONS_EXTENSION_ACTION_CONTEXT_MENU_H_ +#define CHROME_BROWSER_COCOA_EXTENSIONS_EXTENSION_ACTION_CONTEXT_MENU_H_ + +#include "base/ref_counted.h" + +#import <Cocoa/Cocoa.h> + +class Extension; +class AsyncUninstaller; + +// A context menu used by the Browser and Page Action components that appears +// if a user right-clicks the view of the given extension. +@interface ExtensionActionContextMenu : NSMenu { + // The extension that this menu belongs to. Weak. + Extension* extension_; + + // Used to load the extension icon asynchronously on the I/O thread then show + // the uninstall confirmation dialog. + scoped_refptr<AsyncUninstaller> uninstaller_; +} + +// Initializes and returns a context menu for the given extension. +- (id)initWithExtension:(Extension*)extension; + +@end + +typedef ExtensionActionContextMenu ExtensionActionContextMenuMac; + +#endif // CHROME_BROWSER_COCOA_EXTENSIONS_EXTENSION_ACTION_CONTEXT_MENU_H_ diff --git a/chrome/browser/cocoa/extensions/extension_action_context_menu.mm b/chrome/browser/cocoa/extensions/extension_action_context_menu.mm new file mode 100644 index 0000000..ee3c6ae --- /dev/null +++ b/chrome/browser/cocoa/extensions/extension_action_context_menu.mm @@ -0,0 +1,195 @@ +// 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 "chrome/browser/cocoa/extensions/extension_action_context_menu.h" + +#include "app/l10n_util_mac.h" +#include "base/sys_string_conversions.h" +#include "base/task.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/extensions/extension_install_ui.h" +#include "chrome/browser/extensions/extensions_service.h" +#include "chrome/browser/profile.h" +#include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_constants.h" +#include "chrome/common/url_constants.h" +#include "grit/generated_resources.h" + +// A class that loads the extension icon on the I/O thread before showing the +// confirmation dialog to uninstall the given extension. +// Also acts as the extension's UI delegate in order to display the dialog. +class AsyncUninstaller : public base::RefCountedThreadSafe<AsyncUninstaller>, + public ExtensionInstallUI::Delegate { + public: + AsyncUninstaller(Extension* extension) : extension_(extension) {} + + // Load the uninstall icon then show the confirmation dialog when finished. + void LoadExtensionIconThenConfirmUninstall() { + ChromeThread::PostTask(ChromeThread::FILE, FROM_HERE, + NewRunnableMethod(this, &AsyncUninstaller::LoadIconOnFileThread, + &uninstall_icon_)); + } + + void Cancel() { + uninstall_icon_.reset(); + extension_ = NULL; + } + + // Overridden by ExtensionInstallUI::Delegate. + virtual void InstallUIProceed() { + Browser* browser = BrowserList::GetLastActive(); + // GetLastActive() returns NULL during testing. + if (!browser) + return; + browser->profile()->GetExtensionsService()-> + UninstallExtension(extension_->id(), false); + } + + virtual void InstallUIAbort() {} + + private: + friend class base::RefCountedThreadSafe<AsyncUninstaller>; + ~AsyncUninstaller() {} + + void LoadIconOnFileThread(scoped_ptr<SkBitmap>* uninstall_icon) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::FILE)); + Extension::DecodeIcon(extension_, Extension::EXTENSION_ICON_LARGE, + uninstall_icon); + ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, + NewRunnableMethod(this, &AsyncUninstaller::ShowConfirmationDialog, + uninstall_icon)); + } + + void ShowConfirmationDialog(scoped_ptr<SkBitmap>* uninstall_icon) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + // If |extension_| is NULL, then the action was cancelled. Bail. + if (!extension_) + return; + + Browser* browser = BrowserList::GetLastActive(); + // GetLastActive() returns NULL during testing. + if (!browser) + return; + + ExtensionInstallUI client(browser->profile()); + client.ConfirmUninstall(this, extension_, uninstall_icon->get()); + } + + // The extension that we're loading the icon for. Weak. + Extension* extension_; + + // The uninstall icon shown by the confirmation dialog. + scoped_ptr<SkBitmap> uninstall_icon_; + + DISALLOW_COPY_AND_ASSIGN(AsyncUninstaller); +}; + +@interface ExtensionActionContextMenu(Private) +// Callback for the context menu items. +- (void)dispatch:(id)menuItem; +@end + +@implementation ExtensionActionContextMenu + +namespace { +// Enum of menu item choices to their respective indices. +// NOTE: You MUST keep this in sync with the |menuItems| NSArray below. +enum { + kExtensionContextName = 0, + kExtensionContextOptions = 2, + kExtensionContextDisable = 3, + kExtensionContextUninstall = 4, + kExtensionContextManage = 6 +}; +} // namespace + +- (id)initWithExtension:(Extension*)extension { + if ((self = [super initWithTitle:@""])) { + extension_ = extension; + + NSArray* menuItems = [NSArray arrayWithObjects: + base::SysUTF8ToNSString(extension->name()), + [NSMenuItem separatorItem], + l10n_util::GetNSStringWithFixup(IDS_EXTENSIONS_OPTIONS), + l10n_util::GetNSStringWithFixup(IDS_EXTENSIONS_DISABLE), + l10n_util::GetNSStringWithFixup(IDS_EXTENSIONS_UNINSTALL), + [NSMenuItem separatorItem], + l10n_util::GetNSStringWithFixup(IDS_MANAGE_EXTENSIONS), + nil]; + + for (id item in menuItems) { + if ([item isKindOfClass:[NSMenuItem class]]) { + [self addItem:item]; + } else if ([item isKindOfClass:[NSString class]]) { + NSMenuItem* itemObj = [self addItemWithTitle:item + action:@selector(dispatch:) + keyEquivalent:@""]; + // The tag should correspond to the enum above. + // NOTE: The enum and the order of the menu items MUST be in sync. + [itemObj setTag:[self indexOfItem:itemObj]]; + + // Disable the 'Options' item if there are no options to set. + if ([itemObj tag] == kExtensionContextOptions && + extension_->options_url().spec().length() <= 0) { + // Setting the target to nil will disable the item. For some reason + // setDisabled:NO does not work. + [itemObj setTarget:nil]; + } else { + [itemObj setTarget:self]; + } + } + } + + return self; + } + return nil; +} + +- (void)dispatch:(id)menuItem { + Browser* browser = BrowserList::GetLastActive(); + // GetLastActive() returns NULL during testing. + if (!browser) + return; + + Profile* profile = browser->profile(); + + NSMenuItem* item = (NSMenuItem*)menuItem; + switch ([item tag]) { + case kExtensionContextName: { + GURL url(std::string(extension_urls::kGalleryBrowsePrefix) + + std::string("/detail/") + extension_->id()); + browser->OpenURL(url, GURL(), NEW_FOREGROUND_TAB, PageTransition::LINK); + break; + } + case kExtensionContextOptions: { + DCHECK(!extension_->options_url().is_empty()); + browser->OpenURL(extension_->options_url(), GURL(), + NEW_FOREGROUND_TAB, PageTransition::LINK); + break; + } + case kExtensionContextDisable: { + ExtensionsService* extension_service = profile->GetExtensionsService(); + extension_service->DisableExtension(extension_->id()); + break; + } + case kExtensionContextUninstall: { + if (uninstaller_.get()) + uninstaller_->Cancel(); + + uninstaller_ = new AsyncUninstaller(extension_); + uninstaller_->LoadExtensionIconThenConfirmUninstall(); + break; + } + case kExtensionContextManage: { + browser->OpenURL(GURL(chrome::kChromeUIExtensionsURL), GURL(), + NEW_FOREGROUND_TAB, PageTransition::LINK); + break; + } + default: + NOTREACHED(); + break; + } +} + +@end |