diff options
Diffstat (limited to 'chrome/browser/cocoa')
-rw-r--r-- | chrome/browser/cocoa/menu_controller.h | 26 | ||||
-rw-r--r-- | chrome/browser/cocoa/menu_controller.mm | 46 | ||||
-rw-r--r-- | chrome/browser/cocoa/menu_controller_unittest.mm | 20 | ||||
-rw-r--r-- | chrome/browser/cocoa/toolbar_controller.h | 8 | ||||
-rw-r--r-- | chrome/browser/cocoa/toolbar_controller.mm | 11 | ||||
-rw-r--r-- | chrome/browser/cocoa/wrench_menu_controller.h | 46 | ||||
-rw-r--r-- | chrome/browser/cocoa/wrench_menu_controller.mm | 127 | ||||
-rw-r--r-- | chrome/browser/cocoa/wrench_menu_controller_unittest.mm | 81 |
8 files changed, 340 insertions, 25 deletions
diff --git a/chrome/browser/cocoa/menu_controller.h b/chrome/browser/cocoa/menu_controller.h index 9c3282c..d14d62b 100644 --- a/chrome/browser/cocoa/menu_controller.h +++ b/chrome/browser/cocoa/menu_controller.h @@ -21,11 +21,22 @@ class MenuModel; // that particular item. It is important that the model outlives this object // as it only maintains weak references. @interface MenuController : NSObject { - @private + @protected + menus::MenuModel* model_; // weak scoped_nsobject<NSMenu> menu_; BOOL useWithPopUpButtonCell_; // If YES, 0th item is blank } +@property (nonatomic, assign) menus::MenuModel* model; +// Note that changing this will have no effect if you use +// |-initWithModel:useWithPopUpButtonCell:| or after the first call to |-menu|. +@property (nonatomic) BOOL useWithPopUpButtonCell; + +// NIB-based initializer. This does not create a menu. Clients can set the +// properties of the object and the menu will be created upon the first call to +// |-menu|. Note that the menu will be immutable after creation. +- (id)init; + // Builds a NSMenu from the pre-built model (must not be nil). Changes made // to the contents of the model after calling this will not be noticed. If // the menu will be displayed by a NSPopUpButtonCell, it needs to be of a @@ -34,14 +45,23 @@ class MenuModel; - (id)initWithModel:(menus::MenuModel*)model useWithPopUpButtonCell:(BOOL)useWithCell; -// Access to the constructed menu. +// Access to the constructed menu if the complex initializer was used. If the +// default initializer was used, then this will create the menu on first call. - (NSMenu*)menu; @end // Exposed only for unit testing, do not call directly. -@interface MenuController(PrivateExposedForTesting) +@interface MenuController (PrivateExposedForTesting) - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item; @end +// Protected methods that subclassers can override. +@interface MenuController (Protected) +- (void)addItemToMenu:(NSMenu*)menu + atIndex:(NSInteger)index + fromModel:(menus::MenuModel*)model + modelIndex:(int)modelIndex; +@end + #endif // CHROME_BROWSER_COCOA_MENU_CONTROLLER_H_ diff --git a/chrome/browser/cocoa/menu_controller.mm b/chrome/browser/cocoa/menu_controller.mm index a34469e..047c8dd 100644 --- a/chrome/browser/cocoa/menu_controller.mm +++ b/chrome/browser/cocoa/menu_controller.mm @@ -10,35 +10,37 @@ #include "base/logging.h" #include "base/sys_string_conversions.h" -@interface MenuController(Private) +@interface MenuController (Private) - (NSMenu*)menuFromModel:(menus::MenuModel*)model; - (void)addSeparatorToMenu:(NSMenu*)menu atIndex:(int)index; -- (void)addItemToMenu:(NSMenu*)menu - atIndex:(int)index - fromModel:(menus::MenuModel*)model - modelIndex:(int)modelIndex; @end @implementation MenuController +@synthesize model = model_; +@synthesize useWithPopUpButtonCell = useWithPopUpButtonCell_; + +- (id)init { + self = [super init]; + return self; +} + - (id)initWithModel:(menus::MenuModel*)model useWithPopUpButtonCell:(BOOL)useWithCell { if ((self = [super init])) { - menu_.reset([[self menuFromModel:model] retain]); - // If this is to be used with a NSPopUpButtonCell, add an item at the 0th - // position that's empty. Doing it after the menu has been constructed won't - // complicate creation logic, and since the tags are model indexes, they - // are unaffected by the extra item. - if (useWithCell) { - scoped_nsobject<NSMenuItem> blankItem( - [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]); - [menu_ insertItem:blankItem atIndex:0]; - } + model_ = model; + useWithPopUpButtonCell_ = useWithCell; + [self menu]; } return self; } +- (void)dealloc { + model_ = NULL; + [super dealloc]; +} + // Creates a NSMenu from the given model. If the model has submenus, this can // be invoked recursively. - (NSMenu*)menuFromModel:(menus::MenuModel*)model { @@ -75,7 +77,7 @@ // Adds an item or a hierarchical menu to the item at the |index|, // associated with the entry in the model indentifed by |modelIndex|. - (void)addItemToMenu:(NSMenu*)menu - atIndex:(int)index + atIndex:(NSInteger)index fromModel:(menus::MenuModel*)model modelIndex:(int)modelIndex { NSString* label = @@ -152,6 +154,18 @@ } - (NSMenu*)menu { + if (!menu_ && model_) { + menu_.reset([[self menuFromModel:model_] retain]); + // If this is to be used with a NSPopUpButtonCell, add an item at the 0th + // position that's empty. Doing it after the menu has been constructed won't + // complicate creation logic, and since the tags are model indexes, they + // are unaffected by the extra item. + if (useWithPopUpButtonCell_) { + scoped_nsobject<NSMenuItem> blankItem( + [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]); + [menu_ insertItem:blankItem atIndex:0]; + } + } return menu_.get(); } diff --git a/chrome/browser/cocoa/menu_controller_unittest.mm b/chrome/browser/cocoa/menu_controller_unittest.mm index f41ef42..de0362a 100644 --- a/chrome/browser/cocoa/menu_controller_unittest.mm +++ b/chrome/browser/cocoa/menu_controller_unittest.mm @@ -175,3 +175,23 @@ TEST_F(MenuControllerTest, Validate) { Validate(menu.get(), [menu menu]); } + +TEST_F(MenuControllerTest, DefaultInitializer) { + Delegate delegate; + menus::SimpleMenuModel model(&delegate); + model.AddItem(1, WideToUTF16(L"one")); + model.AddItem(2, WideToUTF16(L"two")); + model.AddItem(3, WideToUTF16(L"three")); + + scoped_nsobject<MenuController> menu([[MenuController alloc] init]); + EXPECT_FALSE([menu menu]); + + [menu setModel:&model]; + [menu setUseWithPopUpButtonCell:NO]; + EXPECT_TRUE([menu menu]); + EXPECT_EQ(3, [[menu menu] numberOfItems]); + + // Check immutability. + model.AddItem(4, WideToUTF16(L"four")); + EXPECT_EQ(3, [[menu menu] numberOfItems]); +} diff --git a/chrome/browser/cocoa/toolbar_controller.h b/chrome/browser/cocoa/toolbar_controller.h index 2b40d45..9d3ad48 100644 --- a/chrome/browser/cocoa/toolbar_controller.h +++ b/chrome/browser/cocoa/toolbar_controller.h @@ -26,7 +26,6 @@ class CommandUpdater; class LocationBar; class LocationBarViewMac; @class MenuButton; -@class MenuController; namespace ToolbarControllerInternal { class MenuDelegate; class PrefObserverBridge; @@ -35,6 +34,7 @@ class Profile; @class ReloadButton; class TabContents; class ToolbarModel; +@class WrenchMenuController; class WrenchMenuModel; // A controller for the toolbar in the browser window. Manages @@ -55,6 +55,7 @@ class WrenchMenuModel; IBOutlet MenuButton* wrenchButton_; IBOutlet AutocompleteTextField* locationBar_; IBOutlet BrowserActionsContainerView* browserActionsContainerView_; + IBOutlet WrenchMenuController* wrenchMenuController_; @private ToolbarModel* toolbarModel_; // weak, one per window @@ -69,12 +70,11 @@ class WrenchMenuModel; scoped_nsobject<BackForwardMenuController> forwardMenuController_; scoped_nsobject<BrowserActionsController> browserActionsController_; - // Lazily-instantiated model, controller, and delegate for the menu on the + // Lazily-instantiated model and delegate for the menu on the // wrench button. Once visible, it will be non-null, but will not // reaped when the menu is hidden once it is initially shown. scoped_ptr<ToolbarControllerInternal::MenuDelegate> menuDelegate_; scoped_ptr<WrenchMenuModel> wrenchMenuModel_; - scoped_nsobject<MenuController> wrenchMenuController_; // Used for monitoring the optional toolbar button prefs. scoped_ptr<ToolbarControllerInternal::PrefObserverBridge> prefObserver_; @@ -157,6 +157,7 @@ class WrenchMenuModel; // Return the BrowserActionsController for this toolbar. - (BrowserActionsController*)browserActionsController; + @end // A set of private methods used by subclasses. Do not call these directly @@ -178,6 +179,7 @@ class WrenchMenuModel; - (NSArray*)toolbarViews; - (void)showOptionalHomeButton; - (void)installWrenchMenu; +- (WrenchMenuController*)wrenchMenuController; // Return a hover button for the current event. - (NSButton*)hoverButtonForEvent:(NSEvent*)theEvent; @end diff --git a/chrome/browser/cocoa/toolbar_controller.mm b/chrome/browser/cocoa/toolbar_controller.mm index 1a3bc74..dab9537 100644 --- a/chrome/browser/cocoa/toolbar_controller.mm +++ b/chrome/browser/cocoa/toolbar_controller.mm @@ -8,6 +8,7 @@ #include "app/l10n_util_mac.h" #include "app/menus/accelerator_cocoa.h" +#include "app/menus/menu_model.h" #include "base/keyboard_codes.h" #include "base/mac_util.h" #include "base/nsimage_cache_mac.h" @@ -31,6 +32,7 @@ #import "chrome/browser/cocoa/menu_controller.h" #import "chrome/browser/cocoa/reload_button.h" #import "chrome/browser/cocoa/toolbar_view.h" +#import "chrome/browser/cocoa/wrench_menu_controller.h" #include "chrome/browser/net/url_fixer_upper.h" #include "chrome/browser/pref_service.h" #include "chrome/browser/profile.h" @@ -511,12 +513,15 @@ class PrefObserverBridge : public NotificationObserver { menuDelegate_.reset(new ToolbarControllerInternal::MenuDelegate(browser_)); wrenchMenuModel_.reset(new WrenchMenuModel(menuDelegate_.get(), browser_)); - wrenchMenuController_.reset( - [[MenuController alloc] initWithModel:wrenchMenuModel_.get() - useWithPopUpButtonCell:YES]); + [wrenchMenuController_ setModel:wrenchMenuModel_.get()]; + [wrenchMenuController_ setUseWithPopUpButtonCell:YES]; [wrenchButton_ setAttachedMenu:[wrenchMenuController_ menu]]; } +- (WrenchMenuController*)wrenchMenuController { + return wrenchMenuController_; +} + - (void)prefChanged:(std::wstring*)prefName { if (!prefName) return; if (*prefName == prefs::kShowHomeButton) { diff --git a/chrome/browser/cocoa/wrench_menu_controller.h b/chrome/browser/cocoa/wrench_menu_controller.h new file mode 100644 index 0000000..6ec1f55 --- /dev/null +++ b/chrome/browser/cocoa/wrench_menu_controller.h @@ -0,0 +1,46 @@ +// 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_WRENCH_MENU_CONTROLLER_H_ +#define CHROME_BROWSER_COCOA_WRENCH_MENU_CONTROLLER_H_ + +#import <Cocoa/Cocoa.h> + +#import "base/cocoa_protocols_mac.h" +#import "chrome/browser/cocoa/menu_controller.h" + +@class ToolbarController; +class WrenchMenuModel; + +// The Wrench menu has a creative layout, with buttons in menu items. There is +// a cross-platform model for this special menu, but on the Mac it's easier to +// get spacing and alignment precisely right using a NIB. To do that, we +// subclass the generic MenuController implementation and special-case the two +// items that require specific layout and load them from the NIB. +// +// This object is instantiated in Toolbar.xib and is configured by the +// ToolbarController. +@interface WrenchMenuController : MenuController <NSMenuDelegate> { + IBOutlet NSView* editItem_; + IBOutlet NSSegmentedControl* editControl_; + + IBOutlet NSView* zoomItem_; + IBOutlet NSButton* zoomPlus_; + IBOutlet NSButton* zoomDisplay_; + IBOutlet NSButton* zoomMinus_; + IBOutlet NSButton* zoomFullScreen_; +} + +// Designated initializer; called within the NIB. +- (id)init; + +// Used to dispatch commands from the Wrench menu. The custom items within the +// menu cannot be hooked up directly to First Responder because the window in +// which the controls reside is not the BrowserWindowController, but a +// NSCarbonMenuWindow; this screws up the typical |-commandDispatch:| system. +- (IBAction)dispatchWrenchMenuCommand:(id)sender; + +@end + +#endif // CHROME_BROWSER_COCOA_WRENCH_MENU_CONTROLLER_H_ diff --git a/chrome/browser/cocoa/wrench_menu_controller.mm b/chrome/browser/cocoa/wrench_menu_controller.mm new file mode 100644 index 0000000..0f31548 --- /dev/null +++ b/chrome/browser/cocoa/wrench_menu_controller.mm @@ -0,0 +1,127 @@ +// 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/wrench_menu_controller.h" + +#include "app/menus/menu_model.h" +#include "base/sys_string_conversions.h" +#include "chrome/app/chrome_dll_resource.h" +#import "chrome/browser/cocoa/toolbar_controller.h" +#include "chrome/browser/wrench_menu_model.h" + +@interface WrenchMenuController (Private) +- (WrenchMenuModel*)wrenchMenuModel; +- (void)adjustPositioning; +@end + +@implementation WrenchMenuController + +- (id)init { + self = [super init]; + return self; +} + +- (void)addItemToMenu:(NSMenu*)menu + atIndex:(NSInteger)index + fromModel:(menus::MenuModel*)model + modelIndex:(int)modelIndex { + // Non-button item types should be built as normal items. + menus::MenuModel::ItemType type = model->GetTypeAt(modelIndex); + if (type != menus::MenuModel::TYPE_BUTTON_ITEM) { + [super addItemToMenu:menu + atIndex:index + fromModel:model + modelIndex:modelIndex]; + return; + } + + // Handle the special-cased menu items. + int command_id = model->GetCommandIdAt(modelIndex); + scoped_nsobject<NSMenuItem> customItem( + [[NSMenuItem alloc] initWithTitle:@"" + action:nil + keyEquivalent:@""]); + switch (command_id) { + case IDC_EDIT_MENU: + DCHECK(editItem_); + [customItem setView:editItem_]; + break; + case IDC_ZOOM_MENU: + DCHECK(zoomItem_); + [customItem setView:zoomItem_]; + break; + default: + NOTREACHED(); + break; + } + [self adjustPositioning]; + [menu insertItem:customItem.get() atIndex:index]; +} + +- (NSMenu*)menu { + NSMenu* menu = [super menu]; + if (![menu delegate]) { + [menu setDelegate:self]; + } + return menu; +} + +- (void)menuWillOpen:(NSMenu*)menu { + NSString* title = base::SysUTF16ToNSString( + [self wrenchMenuModel]->GetLabelForCommandId(IDC_ZOOM_PERCENT_DISPLAY)); + [[zoomItem_ viewWithTag:IDC_ZOOM_PERCENT_DISPLAY] setTitle:title]; +} + +// Used to dispatch commands from the Wrench menu. The custom items within the +// menu cannot be hooked up directly to First Responder because the window in +// which the controls reside is not the BrowserWindowController, but a +// NSCarbonMenuWindow; this screws up the typical |-commandDispatch:| system. +- (IBAction)dispatchWrenchMenuCommand:(id)sender { + NSInteger tag = [sender tag]; + + // NSSegmentedControls (used for the Edit item) need a little help to get the + // command_id of the pressed item. + if ([sender isKindOfClass:[NSSegmentedControl class]]) + tag = [[sender cell] tagForSegment:[sender selectedSegment]]; + + // The custom views within the Wrench menu are abnormal and keep the menu open + // after a target-action. Close the menu manually. + // TODO(rsesek): It'd be great if the zoom buttons didn't have to close the + // menu. See http://crbug.com/48679 for more info. + [menu_ cancelTracking]; + [self wrenchMenuModel]->ExecuteCommand(tag); +} + +- (WrenchMenuModel*)wrenchMenuModel { + return static_cast<WrenchMenuModel*>(model_); +} + +// Fit the localized strings into the Cut/Copy/Paste control, then resize the +// whole menu item accordingly. +- (void)adjustPositioning { + NSRect itemFrame = [editItem_ frame]; + NSRect controlFrame = [editControl_ frame]; + + CGFloat originalControlWidth = NSWidth(controlFrame); + // Maintain the carefully pixel-pushed gap between the edge of the menu and + // the rightmost control. + CGFloat edge = NSWidth(itemFrame) - + (controlFrame.origin.x + originalControlWidth); + + // Resize the edit segmented control to fit the localized strings. + [editControl_ sizeToFit]; + controlFrame = [editControl_ frame]; + CGFloat resizeAmount = NSWidth(controlFrame) - originalControlWidth; + + // Adjust the size of the entire menu item to account for changes in the size + // of the segmented control. + itemFrame.size.width += resizeAmount; + [editItem_ setFrame:itemFrame]; + + // Keep the spacing between the right edges of the menu and the control. + controlFrame.origin.x = NSWidth(itemFrame) - edge - NSWidth(controlFrame); + [editControl_ setFrame:controlFrame]; +} + +@end // @implementation WrenchMenuController diff --git a/chrome/browser/cocoa/wrench_menu_controller_unittest.mm b/chrome/browser/cocoa/wrench_menu_controller_unittest.mm new file mode 100644 index 0000000..6eafe33 --- /dev/null +++ b/chrome/browser/cocoa/wrench_menu_controller_unittest.mm @@ -0,0 +1,81 @@ +// 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. + +#include "base/scoped_nsobject.h" +#include "chrome/app/chrome_dll_resource.h" +#include "chrome/browser/cocoa/browser_test_helper.h" +#import "chrome/browser/cocoa/cocoa_test_helper.h" +#import "chrome/browser/cocoa/toolbar_controller.h" +#import "chrome/browser/cocoa/wrench_menu_controller.h" +#import "chrome/browser/cocoa/view_resizer_pong.h" +#include "chrome/browser/wrench_menu_model.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +namespace { + +class MockWrenchMenuModel : public WrenchMenuModel { + public: + MockWrenchMenuModel() : WrenchMenuModel() {} + MOCK_METHOD1(ExecuteCommand, void(int command_id)); +}; + +class WrenchMenuControllerTest : public CocoaTest { + public: + void SetUp() { + Browser* browser = helper_.browser(); + resize_delegate_.reset([[ViewResizerPong alloc] init]); + toolbar_controller_.reset( + [[ToolbarController alloc] initWithModel:browser->toolbar_model() + commands:browser->command_updater() + profile:helper_.profile() + browser:browser + resizeDelegate:resize_delegate_.get()]); + EXPECT_TRUE([toolbar_controller_ view]); + NSView* parent = [test_window() contentView]; + [parent addSubview:[toolbar_controller_ view]]; + } + + WrenchMenuController* controller() { + return [toolbar_controller_ wrenchMenuController]; + } + + BrowserTestHelper helper_; + scoped_nsobject<ViewResizerPong> resize_delegate_; + MockWrenchMenuModel fake_model_; + scoped_nsobject<ToolbarController> toolbar_controller_; +}; + +TEST_F(WrenchMenuControllerTest, Initialized) { + EXPECT_TRUE([controller() menu]); + EXPECT_GE([[controller() menu] numberOfItems], 5); +} + +TEST_F(WrenchMenuControllerTest, DispatchSimple) { + scoped_nsobject<NSButton> button([[NSButton alloc] init]); + [button setTag:IDC_ZOOM_PLUS]; + + // Set fake model to test dispatching. + EXPECT_CALL(fake_model_, ExecuteCommand(IDC_ZOOM_PLUS)); + [controller() setModel:&fake_model_]; + + [controller() dispatchWrenchMenuCommand:button.get()]; +} + +TEST_F(WrenchMenuControllerTest, DispatchSegmentedControl) { + // Set fake model to test dispatching. + EXPECT_CALL(fake_model_, ExecuteCommand(IDC_CUT)); + [controller() setModel:&fake_model_]; + + scoped_nsobject<NSSegmentedControl> control( + [[NSSegmentedControl alloc] init]); + [control setSegmentCount:2]; + [[control cell] setTag:IDC_CUT forSegment:0]; + [[control cell] setSelectedSegment:0]; + + [controller() dispatchWrenchMenuCommand:control.get()]; +} + +} // namespace |