[Mac] Ship the MenuController implementation from Chromium.
This commit is contained in:
parent
ee2c696577
commit
22012d41d8
5 changed files with 362 additions and 8 deletions
2
atom.gyp
2
atom.gyp
|
@ -98,6 +98,8 @@
|
||||||
'browser/ui/accelerator_util.h',
|
'browser/ui/accelerator_util.h',
|
||||||
'browser/ui/accelerator_util_mac.mm',
|
'browser/ui/accelerator_util_mac.mm',
|
||||||
'browser/ui/accelerator_util_win.cc',
|
'browser/ui/accelerator_util_win.cc',
|
||||||
|
'browser/ui/atom_menu_controller_mac.h',
|
||||||
|
'browser/ui/atom_menu_controller_mac.mm',
|
||||||
'browser/ui/file_dialog.h',
|
'browser/ui/file_dialog.h',
|
||||||
'browser/ui/file_dialog_mac.mm',
|
'browser/ui/file_dialog_mac.mm',
|
||||||
'browser/ui/file_dialog_win.cc',
|
'browser/ui/file_dialog_win.cc',
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
#include "browser/api/atom_api_menu.h"
|
#include "browser/api/atom_api_menu.h"
|
||||||
|
|
||||||
#import "chrome/browser/ui/cocoa/menu_controller.h"
|
#import "browser/ui/atom_menu_controller_mac.h"
|
||||||
|
|
||||||
namespace atom {
|
namespace atom {
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class MenuMac : public Menu {
|
||||||
protected:
|
protected:
|
||||||
virtual void Popup(NativeWindow* window) OVERRIDE;
|
virtual void Popup(NativeWindow* window) OVERRIDE;
|
||||||
|
|
||||||
scoped_nsobject<MenuController> menu_controller_;
|
scoped_nsobject<AtomMenuController> menu_controller_;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class Menu;
|
friend class Menu;
|
||||||
|
|
|
@ -23,9 +23,9 @@ MenuMac::~MenuMac() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuMac::Popup(NativeWindow* native_window) {
|
void MenuMac::Popup(NativeWindow* native_window) {
|
||||||
scoped_nsobject<MenuController> menu_controller(
|
scoped_nsobject<AtomMenuController> menu_controller(
|
||||||
[[MenuController alloc] initWithModel:model_.get()
|
[[AtomMenuController alloc] initWithModel:model_.get()
|
||||||
useWithPopUpButtonCell:NO]);
|
useWithPopUpButtonCell:NO]);
|
||||||
|
|
||||||
NSWindow* window = native_window->GetNativeWindow();
|
NSWindow* window = native_window->GetNativeWindow();
|
||||||
content::WebContents* web_contents = native_window->GetWebContents();
|
content::WebContents* web_contents = native_window->GetWebContents();
|
||||||
|
@ -97,9 +97,9 @@ v8::Handle<v8::Value> Menu::SetApplicationMenu(const v8::Arguments &args) {
|
||||||
if (!menu)
|
if (!menu)
|
||||||
return node::ThrowError("Menu is destroyed");
|
return node::ThrowError("Menu is destroyed");
|
||||||
|
|
||||||
scoped_nsobject<MenuController> menu_controller(
|
scoped_nsobject<AtomMenuController> menu_controller(
|
||||||
[[MenuController alloc] initWithModel:menu->model_.get()
|
[[AtomMenuController alloc] initWithModel:menu->model_.get()
|
||||||
useWithPopUpButtonCell:NO]);
|
useWithPopUpButtonCell:NO]);
|
||||||
MenuMac::FixMenuTitles([menu_controller menu]);
|
MenuMac::FixMenuTitles([menu_controller menu]);
|
||||||
[NSApp setMainMenu:[menu_controller menu]];
|
[NSApp setMainMenu:[menu_controller menu]];
|
||||||
|
|
||||||
|
|
84
browser/ui/atom_menu_controller_mac.h
Normal file
84
browser/ui/atom_menu_controller_mac.h
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||||
|
// Copyright (c) 2012 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 ATOM_BROWSER_UI_ATOM_MENU_CONTROLLER_MAC_H_
|
||||||
|
#define ATOM_BROWSER_UI_ATOM_MENU_CONTROLLER_MAC_H_
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
#include "base/memory/scoped_nsobject.h"
|
||||||
|
#include "base/string16.h"
|
||||||
|
|
||||||
|
namespace ui {
|
||||||
|
class MenuModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A controller for the cross-platform menu model. The menu that's created
|
||||||
|
// has the tag and represented object set for each menu item. The object is a
|
||||||
|
// NSValue holding a pointer to the model for that level of the menu (to
|
||||||
|
// allow for hierarchical menus). The tag is the index into that model for
|
||||||
|
// that particular item. It is important that the model outlives this object
|
||||||
|
// as it only maintains weak references.
|
||||||
|
@interface AtomMenuController : NSObject<NSMenuDelegate> {
|
||||||
|
@protected
|
||||||
|
ui::MenuModel* model_; // weak
|
||||||
|
scoped_nsobject<NSMenu> menu_;
|
||||||
|
BOOL useWithPopUpButtonCell_; // If YES, 0th item is blank
|
||||||
|
BOOL isMenuOpen_;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property(nonatomic, assign) ui::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
|
||||||
|
// slightly different form (0th item is empty). Note this attribute of the menu
|
||||||
|
// cannot be changed after it has been created.
|
||||||
|
- (id)initWithModel:(ui::MenuModel*)model
|
||||||
|
useWithPopUpButtonCell:(BOOL)useWithCell;
|
||||||
|
|
||||||
|
// Programmatically close the constructed menu.
|
||||||
|
- (void)cancel;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// Whether the menu is currently open.
|
||||||
|
- (BOOL)isMenuOpen;
|
||||||
|
|
||||||
|
// NSMenuDelegate methods this class implements. Subclasses should call super
|
||||||
|
// if extending the behavior.
|
||||||
|
- (void)menuWillOpen:(NSMenu*)menu;
|
||||||
|
- (void)menuDidClose:(NSMenu*)menu;
|
||||||
|
|
||||||
|
@end
|
||||||
|
|
||||||
|
// Exposed only for unit testing, do not call directly.
|
||||||
|
@interface AtomMenuController (PrivateExposedForTesting)
|
||||||
|
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item;
|
||||||
|
@end
|
||||||
|
|
||||||
|
// Protected methods that subclassers can override.
|
||||||
|
@interface AtomMenuController (Protected)
|
||||||
|
- (void)addItemToMenu:(NSMenu*)menu
|
||||||
|
atIndex:(NSInteger)index
|
||||||
|
fromModel:(ui::MenuModel*)model;
|
||||||
|
- (NSMenu*)menuFromModel:(ui::MenuModel*)model;
|
||||||
|
// Returns the maximum width for the menu item. Returns -1 to indicate
|
||||||
|
// that there's no maximum width.
|
||||||
|
- (int)maxWidthForMenuModel:(ui::MenuModel*)model
|
||||||
|
modelIndex:(int)modelIndex;
|
||||||
|
@end
|
||||||
|
|
||||||
|
#endif // ATOM_BROWSER_UI_ATOM_MENU_CONTROLLER_MAC_H_
|
268
browser/ui/atom_menu_controller_mac.mm
Normal file
268
browser/ui/atom_menu_controller_mac.mm
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
|
||||||
|
// Copyright (c) 2012 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/ui/atom_menu_controller_mac.h"
|
||||||
|
|
||||||
|
#include "base/logging.h"
|
||||||
|
#include "base/strings/sys_string_conversions.h"
|
||||||
|
#include "ui/base/accelerators/accelerator.h"
|
||||||
|
#include "ui/base/accelerators/platform_accelerator_cocoa.h"
|
||||||
|
#include "ui/base/l10n/l10n_util_mac.h"
|
||||||
|
#include "ui/base/models/simple_menu_model.h"
|
||||||
|
#include "ui/gfx/image/image.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool isLeftButtonEvent(NSEvent* event) {
|
||||||
|
NSEventType type = [event type];
|
||||||
|
return type == NSLeftMouseDown ||
|
||||||
|
type == NSLeftMouseDragged ||
|
||||||
|
type == NSLeftMouseUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isRightButtonEvent(NSEvent* event) {
|
||||||
|
NSEventType type = [event type];
|
||||||
|
return type == NSRightMouseDown ||
|
||||||
|
type == NSRightMouseDragged ||
|
||||||
|
type == NSRightMouseUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isMiddleButtonEvent(NSEvent* event) {
|
||||||
|
if ([event buttonNumber] != 2)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
NSEventType type = [event type];
|
||||||
|
return type == NSOtherMouseDown ||
|
||||||
|
type == NSOtherMouseDragged ||
|
||||||
|
type == NSOtherMouseUp;
|
||||||
|
}
|
||||||
|
|
||||||
|
int EventFlagsFromNSEventWithModifiers(NSEvent* event, NSUInteger modifiers) {
|
||||||
|
int flags = 0;
|
||||||
|
flags |= (modifiers & NSAlphaShiftKeyMask) ? ui::EF_CAPS_LOCK_DOWN : 0;
|
||||||
|
flags |= (modifiers & NSShiftKeyMask) ? ui::EF_SHIFT_DOWN : 0;
|
||||||
|
flags |= (modifiers & NSControlKeyMask) ? ui::EF_CONTROL_DOWN : 0;
|
||||||
|
flags |= (modifiers & NSAlternateKeyMask) ? ui::EF_ALT_DOWN : 0;
|
||||||
|
flags |= (modifiers & NSCommandKeyMask) ? ui::EF_COMMAND_DOWN : 0;
|
||||||
|
flags |= isLeftButtonEvent(event) ? ui::EF_LEFT_MOUSE_BUTTON : 0;
|
||||||
|
flags |= isRightButtonEvent(event) ? ui::EF_RIGHT_MOUSE_BUTTON : 0;
|
||||||
|
flags |= isMiddleButtonEvent(event) ? ui::EF_MIDDLE_MOUSE_BUTTON : 0;
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieves a bitsum of ui::EventFlags from NSEvent.
|
||||||
|
int EventFlagsFromNSEvent(NSEvent* event) {
|
||||||
|
NSUInteger modifiers = [event modifierFlags];
|
||||||
|
return EventFlagsFromNSEventWithModifiers(event, modifiers);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
@interface AtomMenuController (Private)
|
||||||
|
- (void)addSeparatorToMenu:(NSMenu*)menu
|
||||||
|
atIndex:(int)index;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation AtomMenuController
|
||||||
|
|
||||||
|
@synthesize model = model_;
|
||||||
|
@synthesize useWithPopUpButtonCell = useWithPopUpButtonCell_;
|
||||||
|
|
||||||
|
- (id)init {
|
||||||
|
self = [super init];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)initWithModel:(ui::MenuModel*)model
|
||||||
|
useWithPopUpButtonCell:(BOOL)useWithCell {
|
||||||
|
if ((self = [super init])) {
|
||||||
|
model_ = model;
|
||||||
|
useWithPopUpButtonCell_ = useWithCell;
|
||||||
|
[self menu];
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc {
|
||||||
|
[menu_ setDelegate:nil];
|
||||||
|
|
||||||
|
// Close the menu if it is still open. This could happen if a tab gets closed
|
||||||
|
// while its context menu is still open.
|
||||||
|
[self cancel];
|
||||||
|
|
||||||
|
model_ = NULL;
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)cancel {
|
||||||
|
if (isMenuOpen_) {
|
||||||
|
[menu_ cancelTracking];
|
||||||
|
model_->MenuClosed();
|
||||||
|
isMenuOpen_ = NO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a NSMenu from the given model. If the model has submenus, this can
|
||||||
|
// be invoked recursively.
|
||||||
|
- (NSMenu*)menuFromModel:(ui::MenuModel*)model {
|
||||||
|
NSMenu* menu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
|
||||||
|
|
||||||
|
const int count = model->GetItemCount();
|
||||||
|
for (int index = 0; index < count; index++) {
|
||||||
|
if (model->GetTypeAt(index) == ui::MenuModel::TYPE_SEPARATOR)
|
||||||
|
[self addSeparatorToMenu:menu atIndex:index];
|
||||||
|
else
|
||||||
|
[self addItemToMenu:menu atIndex:index fromModel:model];
|
||||||
|
}
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (int)maxWidthForMenuModel:(ui::MenuModel*)model
|
||||||
|
modelIndex:(int)modelIndex {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a separator item at the given index. As the separator doesn't need
|
||||||
|
// anything from the model, this method doesn't need the model index as the
|
||||||
|
// other method below does.
|
||||||
|
- (void)addSeparatorToMenu:(NSMenu*)menu
|
||||||
|
atIndex:(int)index {
|
||||||
|
NSMenuItem* separator = [NSMenuItem separatorItem];
|
||||||
|
[menu insertItem:separator atIndex:index];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds an item or a hierarchical menu to the item at the |index|,
|
||||||
|
// associated with the entry in the model identified by |modelIndex|.
|
||||||
|
- (void)addItemToMenu:(NSMenu*)menu
|
||||||
|
atIndex:(NSInteger)index
|
||||||
|
fromModel:(ui::MenuModel*)model {
|
||||||
|
string16 label16 = model->GetLabelAt(index);
|
||||||
|
NSString* label = l10n_util::FixUpWindowsStyleLabel(label16);
|
||||||
|
scoped_nsobject<NSMenuItem> item(
|
||||||
|
[[NSMenuItem alloc] initWithTitle:label
|
||||||
|
action:@selector(itemSelected:)
|
||||||
|
keyEquivalent:@""]);
|
||||||
|
|
||||||
|
// If the menu item has an icon, set it.
|
||||||
|
gfx::Image icon;
|
||||||
|
if (model->GetIconAt(index, &icon) && !icon.IsEmpty())
|
||||||
|
[item setImage:icon.ToNSImage()];
|
||||||
|
|
||||||
|
ui::MenuModel::ItemType type = model->GetTypeAt(index);
|
||||||
|
if (type == ui::MenuModel::TYPE_SUBMENU) {
|
||||||
|
// Recursively build a submenu from the sub-model at this index.
|
||||||
|
[item setTarget:nil];
|
||||||
|
[item setAction:nil];
|
||||||
|
ui::MenuModel* submenuModel = model->GetSubmenuModelAt(index);
|
||||||
|
NSMenu* submenu =
|
||||||
|
[self menuFromModel:(ui::SimpleMenuModel*)submenuModel];
|
||||||
|
[item setSubmenu:submenu];
|
||||||
|
} else {
|
||||||
|
// The MenuModel works on indexes so we can't just set the command id as the
|
||||||
|
// tag like we do in other menus. Also set the represented object to be
|
||||||
|
// the model so hierarchical menus check the correct index in the correct
|
||||||
|
// model. Setting the target to |self| allows this class to participate
|
||||||
|
// in validation of the menu items.
|
||||||
|
[item setTag:index];
|
||||||
|
[item setTarget:self];
|
||||||
|
NSValue* modelObject = [NSValue valueWithPointer:model];
|
||||||
|
[item setRepresentedObject:modelObject]; // Retains |modelObject|.
|
||||||
|
ui::Accelerator accelerator;
|
||||||
|
if (model->GetAcceleratorAt(index, &accelerator)) {
|
||||||
|
const ui::PlatformAcceleratorCocoa* platformAccelerator =
|
||||||
|
static_cast<const ui::PlatformAcceleratorCocoa*>(
|
||||||
|
accelerator.platform_accelerator());
|
||||||
|
if (platformAccelerator) {
|
||||||
|
[item setKeyEquivalent:platformAccelerator->characters()];
|
||||||
|
[item setKeyEquivalentModifierMask:
|
||||||
|
platformAccelerator->modifier_mask()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[menu insertItem:item atIndex:index];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called before the menu is to be displayed to update the state (enabled,
|
||||||
|
// radio, etc) of each item in the menu. Also will update the title if
|
||||||
|
// the item is marked as "dynamic".
|
||||||
|
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
|
||||||
|
SEL action = [item action];
|
||||||
|
if (action != @selector(itemSelected:))
|
||||||
|
return NO;
|
||||||
|
|
||||||
|
NSInteger modelIndex = [item tag];
|
||||||
|
ui::MenuModel* model =
|
||||||
|
static_cast<ui::MenuModel*>(
|
||||||
|
[[(id)item representedObject] pointerValue]);
|
||||||
|
DCHECK(model);
|
||||||
|
if (model) {
|
||||||
|
BOOL checked = model->IsItemCheckedAt(modelIndex);
|
||||||
|
DCHECK([(id)item isKindOfClass:[NSMenuItem class]]);
|
||||||
|
[(id)item setState:(checked ? NSOnState : NSOffState)];
|
||||||
|
[(id)item setHidden:(!model->IsVisibleAt(modelIndex))];
|
||||||
|
if (model->IsItemDynamicAt(modelIndex)) {
|
||||||
|
// Update the label and the icon.
|
||||||
|
NSString* label =
|
||||||
|
l10n_util::FixUpWindowsStyleLabel(model->GetLabelAt(modelIndex));
|
||||||
|
[(id)item setTitle:label];
|
||||||
|
|
||||||
|
gfx::Image icon;
|
||||||
|
model->GetIconAt(modelIndex, &icon);
|
||||||
|
[(id)item setImage:icon.IsEmpty() ? nil : icon.ToNSImage()];
|
||||||
|
}
|
||||||
|
return model->IsEnabledAt(modelIndex);
|
||||||
|
}
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when the user chooses a particular menu item. |sender| is the menu
|
||||||
|
// item chosen.
|
||||||
|
- (void)itemSelected:(id)sender {
|
||||||
|
NSInteger modelIndex = [sender tag];
|
||||||
|
ui::MenuModel* model =
|
||||||
|
static_cast<ui::MenuModel*>(
|
||||||
|
[[sender representedObject] pointerValue]);
|
||||||
|
DCHECK(model);
|
||||||
|
if (model) {
|
||||||
|
int event_flags = EventFlagsFromNSEvent([NSApp currentEvent]);
|
||||||
|
model->ActivatedAt(modelIndex, event_flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSMenu*)menu {
|
||||||
|
if (!menu_ && model_) {
|
||||||
|
menu_.reset([[self menuFromModel:model_] retain]);
|
||||||
|
[menu_ setDelegate:self];
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isMenuOpen {
|
||||||
|
return isMenuOpen_;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)menuWillOpen:(NSMenu*)menu {
|
||||||
|
isMenuOpen_ = YES;
|
||||||
|
model_->MenuWillShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)menuDidClose:(NSMenu*)menu {
|
||||||
|
if (isMenuOpen_) {
|
||||||
|
model_->MenuClosed();
|
||||||
|
isMenuOpen_ = NO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
Loading…
Reference in a new issue