summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/cocoa')
-rw-r--r--chrome/browser/cocoa/menu_controller.h26
-rw-r--r--chrome/browser/cocoa/menu_controller.mm46
-rw-r--r--chrome/browser/cocoa/menu_controller_unittest.mm20
-rw-r--r--chrome/browser/cocoa/toolbar_controller.h8
-rw-r--r--chrome/browser/cocoa/toolbar_controller.mm11
-rw-r--r--chrome/browser/cocoa/wrench_menu_controller.h46
-rw-r--r--chrome/browser/cocoa/wrench_menu_controller.mm127
-rw-r--r--chrome/browser/cocoa/wrench_menu_controller_unittest.mm81
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