Merge pull request #2682 from atom/menu-item-role

Add "role" attribute for MenuItem
This commit is contained in:
Cheng Zhao 2015-09-02 10:30:41 +08:00
commit bfa33de792
13 changed files with 409 additions and 346 deletions

View file

@ -107,6 +107,10 @@ void Menu::SetSublabel(int index, const base::string16& sublabel) {
model_->SetSublabel(index, sublabel); model_->SetSublabel(index, sublabel);
} }
void Menu::SetRole(int index, const base::string16& role) {
model_->SetRole(index, role);
}
void Menu::Clear() { void Menu::Clear() {
model_->Clear(); model_->Clear();
} }
@ -154,6 +158,7 @@ void Menu::BuildPrototype(v8::Isolate* isolate,
.SetMethod("insertSubMenu", &Menu::InsertSubMenuAt) .SetMethod("insertSubMenu", &Menu::InsertSubMenuAt)
.SetMethod("setIcon", &Menu::SetIcon) .SetMethod("setIcon", &Menu::SetIcon)
.SetMethod("setSublabel", &Menu::SetSublabel) .SetMethod("setSublabel", &Menu::SetSublabel)
.SetMethod("setRole", &Menu::SetRole)
.SetMethod("clear", &Menu::Clear) .SetMethod("clear", &Menu::Clear)
.SetMethod("getIndexOfCommandId", &Menu::GetIndexOfCommandId) .SetMethod("getIndexOfCommandId", &Menu::GetIndexOfCommandId)
.SetMethod("getItemCount", &Menu::GetItemCount) .SetMethod("getItemCount", &Menu::GetItemCount)

View file

@ -73,6 +73,7 @@ class Menu : public mate::Wrappable,
Menu* menu); Menu* menu);
void SetIcon(int index, const gfx::Image& image); void SetIcon(int index, const gfx::Image& image);
void SetSublabel(int index, const base::string16& sublabel); void SetSublabel(int index, const base::string16& sublabel);
void SetRole(int index, const base::string16& role);
void Clear(); void Clear();
int GetIndexOfCommandId(int command_id); int GetIndexOfCommandId(int command_id);
int GetItemCount() const; int GetItemCount() const;

View file

@ -62,6 +62,12 @@ BrowserWindow::loadUrl = -> @webContents.loadUrl.apply @webContents, arguments
BrowserWindow::send = -> @webContents.send.apply @webContents, arguments BrowserWindow::send = -> @webContents.send.apply @webContents, arguments
# Be compatible with old API. # Be compatible with old API.
BrowserWindow::undo = -> @webContents.undo()
BrowserWindow::redo = -> @webContents.redo()
BrowserWindow::cut = -> @webContents.cut()
BrowserWindow::copy = -> @webContents.copy()
BrowserWindow::paste = -> @webContents.paste()
BrowserWindow::selectAll = -> @webContents.selectAll()
BrowserWindow::restart = -> @webContents.reload() BrowserWindow::restart = -> @webContents.reload()
BrowserWindow::getUrl = -> @webContents.getUrl() BrowserWindow::getUrl = -> @webContents.getUrl()
BrowserWindow::reload = -> @webContents.reload.apply @webContents, arguments BrowserWindow::reload = -> @webContents.reload.apply @webContents, arguments

View file

@ -3,18 +3,30 @@ v8Util = process.atomBinding 'v8_util'
nextCommandId = 0 nextCommandId = 0
# Maps role to methods of webContents
rolesMap =
undo: 'undo'
redo: 'redo'
cut: 'cut'
copy: 'copy'
paste: 'paste'
selectall: 'selectAll'
minimize: 'minimize'
close: 'close'
class MenuItem class MenuItem
@types = ['normal', 'separator', 'submenu', 'checkbox', 'radio'] @types = ['normal', 'separator', 'submenu', 'checkbox', 'radio']
constructor: (options) -> constructor: (options) ->
Menu = require 'menu' Menu = require 'menu'
{click, @selector, @type, @label, @sublabel, @accelerator, @icon, @enabled, @visible, @checked, @submenu} = options {click, @selector, @type, @role, @label, @sublabel, @accelerator, @icon, @enabled, @visible, @checked, @submenu} = options
@type = 'submenu' if not @type? and @submenu? @type = 'submenu' if not @type? and @submenu?
throw new Error('Invalid submenu') if @type is 'submenu' and @submenu?.constructor isnt Menu throw new Error('Invalid submenu') if @type is 'submenu' and @submenu?.constructor isnt Menu
@overrideReadOnlyProperty 'type', 'normal' @overrideReadOnlyProperty 'type', 'normal'
@overrideReadOnlyProperty 'role'
@overrideReadOnlyProperty 'accelerator' @overrideReadOnlyProperty 'accelerator'
@overrideReadOnlyProperty 'icon' @overrideReadOnlyProperty 'icon'
@overrideReadOnlyProperty 'submenu' @overrideReadOnlyProperty 'submenu'
@ -27,12 +39,14 @@ class MenuItem
throw new Error("Unknown menu type #{@type}") if MenuItem.types.indexOf(@type) is -1 throw new Error("Unknown menu type #{@type}") if MenuItem.types.indexOf(@type) is -1
@commandId = ++nextCommandId @commandId = ++nextCommandId
@click = => @click = (focusedWindow) =>
# Manually flip the checked flags when clicked. # Manually flip the checked flags when clicked.
@checked = !@checked if @type in ['checkbox', 'radio'] @checked = !@checked if @type in ['checkbox', 'radio']
if typeof click is 'function' if @role and rolesMap[@role] and process.platform isnt 'darwin'
click this, BrowserWindow.getFocusedWindow() focusedWindow?[rolesMap[@role]]()
else if typeof click is 'function'
click this, focusedWindow
else if typeof @selector is 'string' else if typeof @selector is 'string'
Menu.sendActionToFirstResponder @selector Menu.sendActionToFirstResponder @selector

View file

@ -67,7 +67,8 @@ Menu::_init = ->
isCommandIdVisible: (commandId) => @commandsMap[commandId]?.visible isCommandIdVisible: (commandId) => @commandsMap[commandId]?.visible
getAcceleratorForCommandId: (commandId) => @commandsMap[commandId]?.accelerator getAcceleratorForCommandId: (commandId) => @commandsMap[commandId]?.accelerator
getIconForCommandId: (commandId) => @commandsMap[commandId]?.icon getIconForCommandId: (commandId) => @commandsMap[commandId]?.icon
executeCommand: (commandId) => @commandsMap[commandId]?.click() executeCommand: (commandId) =>
@commandsMap[commandId]?.click BrowserWindow.getFocusedWindow()
menuWillShow: => menuWillShow: =>
# Make sure radio groups have at least one menu item seleted. # Make sure radio groups have at least one menu item seleted.
for id, group of @groupsMap for id, group of @groupsMap
@ -115,6 +116,7 @@ Menu::insert = (pos, item) ->
@setSublabel pos, item.sublabel if item.sublabel? @setSublabel pos, item.sublabel if item.sublabel?
@setIcon pos, item.icon if item.icon? @setIcon pos, item.icon if item.icon?
@setRole pos, item.role if item.role?
# Make menu accessable to items. # Make menu accessable to items.
item.overrideReadOnlyProperty 'menu', this item.overrideReadOnlyProperty 'menu', this

View file

@ -36,238 +36,176 @@ app.once('ready', function() {
if (Menu.getApplicationMenu()) if (Menu.getApplicationMenu())
return; return;
var template; var template = [
{
label: 'Edit',
submenu: [
{
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
role: 'undo'
},
{
label: 'Redo',
accelerator: 'Shift+CmdOrCtrl+Z',
role: 'redo'
},
{
type: 'separator'
},
{
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
role: 'cut'
},
{
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
role: 'copy'
},
{
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
role: 'paste'
},
{
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
role: 'selectall'
},
]
},
{
label: 'View',
submenu: [
{
label: 'Reload',
accelerator: 'CmdOrCtrl+R',
click: function(item, focusedWindow) {
if (focusedWindow)
focusedWindow.reload();
}
},
{
label: 'Toggle Full Screen',
accelerator: (function() {
if (process.platform == 'darwin')
return 'Ctrl+Command+F';
else
return 'F11';
})(),
click: function(item, focusedWindow) {
if (focusedWindow)
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
}
},
{
label: 'Toggle Developer Tools',
accelerator: (function() {
if (process.platform == 'darwin')
return 'Alt+Command+I';
else
return 'Ctrl+Shift+I';
})(),
click: function(item, focusedWindow) {
if (focusedWindow)
focusedWindow.toggleDevTools();
}
},
]
},
{
label: 'Window',
role: 'window',
submenu: [
{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
role: 'minimize'
},
{
label: 'Close',
accelerator: 'CmdOrCtrl+W',
role: 'close'
},
]
},
{
label: 'Help',
role: 'help',
submenu: [
{
label: 'Learn More',
click: function() { require('shell').openExternal('http://electron.atom.io') }
},
{
label: 'Documentation',
click: function() { require('shell').openExternal('https://github.com/atom/electron/tree/master/docs#readme') }
},
{
label: 'Community Discussions',
click: function() { require('shell').openExternal('https://discuss.atom.io/c/electron') }
},
{
label: 'Search Issues',
click: function() { require('shell').openExternal('https://github.com/atom/electron/issues') }
}
]
},
];
if (process.platform == 'darwin') { if (process.platform == 'darwin') {
template = [ template.unshift({
label: 'Electron',
submenu: [
{
label: 'About Electron',
role: 'about'
},
{
type: 'separator'
},
{
label: 'Services',
role: 'services',
submenu: []
},
{
type: 'separator'
},
{
label: 'Hide Electron',
accelerator: 'Command+H',
role: 'hide'
},
{
label: 'Hide Others',
accelerator: 'Command+Shift+H',
role: 'hideothers:'
},
{
label: 'Show All',
role: 'unhide:'
},
{
type: 'separator'
},
{
label: 'Quit',
accelerator: 'Command+Q',
click: function() { app.quit(); }
},
]
});
template[3].submenu.push(
{ {
label: 'Electron', type: 'separator'
submenu: [
{
label: 'About Electron',
selector: 'orderFrontStandardAboutPanel:'
},
{
type: 'separator'
},
{
label: 'Services',
submenu: []
},
{
type: 'separator'
},
{
label: 'Hide Electron',
accelerator: 'Command+H',
selector: 'hide:'
},
{
label: 'Hide Others',
accelerator: 'Command+Shift+H',
selector: 'hideOtherApplications:'
},
{
label: 'Show All',
selector: 'unhideAllApplications:'
},
{
type: 'separator'
},
{
label: 'Quit',
accelerator: 'Command+Q',
click: function() { app.quit(); }
},
]
}, },
{ {
label: 'Edit', label: 'Bring All to Front',
submenu: [ role: 'front'
{
label: 'Undo',
accelerator: 'Command+Z',
selector: 'undo:'
},
{
label: 'Redo',
accelerator: 'Shift+Command+Z',
selector: 'redo:'
},
{
type: 'separator'
},
{
label: 'Cut',
accelerator: 'Command+X',
selector: 'cut:'
},
{
label: 'Copy',
accelerator: 'Command+C',
selector: 'copy:'
},
{
label: 'Paste',
accelerator: 'Command+V',
selector: 'paste:'
},
{
label: 'Select All',
accelerator: 'Command+A',
selector: 'selectAll:'
},
]
},
{
label: 'View',
submenu: [
{
label: 'Reload',
accelerator: 'Command+R',
click: function() {
var focusedWindow = BrowserWindow.getFocusedWindow();
if (focusedWindow)
focusedWindow.reload();
}
},
{
label: 'Toggle Full Screen',
accelerator: 'Ctrl+Command+F',
click: function() {
var focusedWindow = BrowserWindow.getFocusedWindow();
if (focusedWindow)
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
}
},
{
label: 'Toggle Developer Tools',
accelerator: 'Alt+Command+I',
click: function() {
var focusedWindow = BrowserWindow.getFocusedWindow();
if (focusedWindow)
focusedWindow.toggleDevTools();
}
},
]
},
{
label: 'Window',
submenu: [
{
label: 'Minimize',
accelerator: 'Command+M',
selector: 'performMiniaturize:'
},
{
label: 'Close',
accelerator: 'Command+W',
selector: 'performClose:'
},
{
type: 'separator'
},
{
label: 'Bring All to Front',
selector: 'arrangeInFront:'
},
]
},
{
label: 'Help',
submenu: [
{
label: 'Learn More',
click: function() { require('shell').openExternal('http://electron.atom.io') }
},
{
label: 'Documentation',
click: function() { require('shell').openExternal('https://github.com/atom/electron/tree/master/docs#readme') }
},
{
label: 'Community Discussions',
click: function() { require('shell').openExternal('https://discuss.atom.io/c/electron') }
},
{
label: 'Search Issues',
click: function() { require('shell').openExternal('https://github.com/atom/electron/issues') }
}
]
} }
]; );
} else {
template = [
{
label: '&File',
submenu: [
{
label: '&Open',
accelerator: 'Ctrl+O',
},
{
label: '&Close',
accelerator: 'Ctrl+W',
click: function() {
var focusedWindow = BrowserWindow.getFocusedWindow();
if (focusedWindow)
focusedWindow.close();
}
},
]
},
{
label: '&View',
submenu: [
{
label: '&Reload',
accelerator: 'Ctrl+R',
click: function() {
var focusedWindow = BrowserWindow.getFocusedWindow();
if (focusedWindow)
focusedWindow.reload();
}
},
{
label: 'Toggle &Full Screen',
accelerator: 'F11',
click: function() {
var focusedWindow = BrowserWindow.getFocusedWindow();
if (focusedWindow)
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
}
},
{
label: 'Toggle &Developer Tools',
accelerator: 'Shift+Ctrl+I',
click: function() {
var focusedWindow = BrowserWindow.getFocusedWindow();
if (focusedWindow)
focusedWindow.toggleDevTools();
}
},
]
},
{
label: 'Help',
submenu: [
{
label: 'Learn More',
click: function() { require('shell').openExternal('http://electron.atom.io') }
},
{
label: 'Documentation',
click: function() { require('shell').openExternal('https://github.com/atom/electron/tree/master/docs#readme') }
},
{
label: 'Community Discussions',
click: function() { require('shell').openExternal('https://discuss.atom.io/c/electron') }
},
{
label: 'Search Issues',
click: function() { require('shell').openExternal('https://github.com/atom/electron/issues') }
}
]
}
];
} }
var menu = Menu.buildFromTemplate(template); var menu = Menu.buildFromTemplate(template);

View file

@ -4,6 +4,8 @@
#include "atom/browser/ui/atom_menu_model.h" #include "atom/browser/ui/atom_menu_model.h"
#include "base/stl_util.h"
namespace atom { namespace atom {
AtomMenuModel::AtomMenuModel(Delegate* delegate) AtomMenuModel::AtomMenuModel(Delegate* delegate)
@ -14,6 +16,17 @@ AtomMenuModel::AtomMenuModel(Delegate* delegate)
AtomMenuModel::~AtomMenuModel() { AtomMenuModel::~AtomMenuModel() {
} }
void AtomMenuModel::SetRole(int index, const base::string16& role) {
roles_[index] = role;
}
base::string16 AtomMenuModel::GetRoleAt(int index) {
if (ContainsKey(roles_, index))
return roles_[index];
else
return base::string16();
}
void AtomMenuModel::MenuClosed() { void AtomMenuModel::MenuClosed() {
ui::SimpleMenuModel::MenuClosed(); ui::SimpleMenuModel::MenuClosed();
FOR_EACH_OBSERVER(Observer, observers_, MenuClosed()); FOR_EACH_OBSERVER(Observer, observers_, MenuClosed());

View file

@ -5,6 +5,8 @@
#ifndef ATOM_BROWSER_UI_ATOM_MENU_MODEL_H_ #ifndef ATOM_BROWSER_UI_ATOM_MENU_MODEL_H_
#define ATOM_BROWSER_UI_ATOM_MENU_MODEL_H_ #define ATOM_BROWSER_UI_ATOM_MENU_MODEL_H_
#include <map>
#include "base/observer_list.h" #include "base/observer_list.h"
#include "ui/base/models/simple_menu_model.h" #include "ui/base/models/simple_menu_model.h"
@ -31,12 +33,16 @@ class AtomMenuModel : public ui::SimpleMenuModel {
void AddObserver(Observer* obs) { observers_.AddObserver(obs); } void AddObserver(Observer* obs) { observers_.AddObserver(obs); }
void RemoveObserver(Observer* obs) { observers_.RemoveObserver(obs); } void RemoveObserver(Observer* obs) { observers_.RemoveObserver(obs); }
void SetRole(int index, const base::string16& role);
base::string16 GetRoleAt(int index);
// ui::SimpleMenuModel: // ui::SimpleMenuModel:
void MenuClosed() override; void MenuClosed() override;
private: private:
Delegate* delegate_; // weak ref. Delegate* delegate_; // weak ref.
std::map<int, base::string16> roles_;
ObserverList<Observer> observers_; ObserverList<Observer> observers_;
DISALLOW_COPY_AND_ASSIGN(AtomMenuModel); DISALLOW_COPY_AND_ASSIGN(AtomMenuModel);

View file

@ -59,17 +59,4 @@ class MenuModel;
@end @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;
@end
#endif // ATOM_BROWSER_UI_COCOA_ATOM_MENU_CONTROLLER_H_ #endif // ATOM_BROWSER_UI_COCOA_ATOM_MENU_CONTROLLER_H_

View file

@ -8,16 +8,36 @@
#include "atom/browser/ui/atom_menu_model.h" #include "atom/browser/ui/atom_menu_model.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "ui/base/accelerators/accelerator.h" #include "ui/base/accelerators/accelerator.h"
#include "ui/base/accelerators/platform_accelerator_cocoa.h" #include "ui/base/accelerators/platform_accelerator_cocoa.h"
#include "ui/base/l10n/l10n_util_mac.h" #include "ui/base/l10n/l10n_util_mac.h"
#include "ui/events/cocoa/cocoa_event_utils.h" #include "ui/events/cocoa/cocoa_event_utils.h"
#include "ui/gfx/image/image.h" #include "ui/gfx/image/image.h"
@interface AtomMenuController (Private) namespace {
- (void)addSeparatorToMenu:(NSMenu*)menu
atIndex:(int)index; struct Role {
@end SEL selector;
const char* role;
};
Role kRolesMap[] = {
{ @selector(orderFrontStandardAboutPanel:), "about" },
{ @selector(hide:), "hide" },
{ @selector(hideOtherApplications:), "hideothers" },
{ @selector(unhideAllApplications:), "unhide" },
{ @selector(arrangeInFront:), "front" },
{ @selector(undo:), "undo" },
{ @selector(redo:), "redo" },
{ @selector(cut:), "cut" },
{ @selector(copy:), "copy" },
{ @selector(paste:), "paste" },
{ @selector(selectAll:), "selectall" },
{ @selector(performMiniaturize:), "minimize" },
{ @selector(performClose:), "close" },
};
} // namespace
@implementation AtomMenuController @implementation AtomMenuController
@ -101,7 +121,9 @@
// associated with the entry in the model identified by |modelIndex|. // associated with the entry in the model identified by |modelIndex|.
- (void)addItemToMenu:(NSMenu*)menu - (void)addItemToMenu:(NSMenu*)menu
atIndex:(NSInteger)index atIndex:(NSInteger)index
fromModel:(ui::MenuModel*)model { fromModel:(ui::MenuModel*)ui_model {
atom::AtomMenuModel* model = static_cast<atom::AtomMenuModel*>(ui_model);
base::string16 label16 = model->GetLabelAt(index); base::string16 label16 = model->GetLabelAt(index);
NSString* label = l10n_util::FixUpWindowsStyleLabel(label16); NSString* label = l10n_util::FixUpWindowsStyleLabel(label16);
base::scoped_nsobject<NSMenuItem> item( base::scoped_nsobject<NSMenuItem> item(
@ -124,13 +146,13 @@
[submenu setTitle:[item title]]; [submenu setTitle:[item title]];
[item setSubmenu:submenu]; [item setSubmenu:submenu];
// Hack to set window and help menu. // Set submenu's role.
if ([[item title] isEqualToString:@"Window"] && [submenu numberOfItems] > 0) base::string16 role = model->GetRoleAt(index);
if (role == base::ASCIIToUTF16("window"))
[NSApp setWindowsMenu:submenu]; [NSApp setWindowsMenu:submenu];
else if ([[item title] isEqualToString:@"Help"]) else if (role == base::ASCIIToUTF16("help"))
[NSApp setHelpMenu:submenu]; [NSApp setHelpMenu:submenu];
if ([[item title] isEqualToString:@"Services"] && if (role == base::ASCIIToUTF16("services"))
[submenu numberOfItems] == 0)
[NSApp setServicesMenu:submenu]; [NSApp setServicesMenu:submenu];
} else { } else {
// The MenuModel works on indexes so we can't just set the command id as the // The MenuModel works on indexes so we can't just set the command id as the
@ -139,7 +161,6 @@
// model. Setting the target to |self| allows this class to participate // model. Setting the target to |self| allows this class to participate
// in validation of the menu items. // in validation of the menu items.
[item setTag:index]; [item setTag:index];
[item setTarget:self];
NSValue* modelObject = [NSValue valueWithPointer:model]; NSValue* modelObject = [NSValue valueWithPointer:model];
[item setRepresentedObject:modelObject]; // Retains |modelObject|. [item setRepresentedObject:modelObject]; // Retains |modelObject|.
ui::Accelerator accelerator; ui::Accelerator accelerator;
@ -153,6 +174,19 @@
platformAccelerator->modifier_mask()]; platformAccelerator->modifier_mask()];
} }
} }
// Set menu item's role.
base::string16 role = model->GetRoleAt(index);
if (role.empty()) {
[item setTarget:self];
} else {
for (const Role& pair : kRolesMap) {
if (role == base::ASCIIToUTF16(pair.role)) {
[item setAction:pair.selector];
break;
}
}
}
} }
[menu insertItem:item atIndex:index]; [menu insertItem:item atIndex:index];
} }

View file

@ -48,12 +48,13 @@ selected when you want to limit the user to a specific type. For example:
] ]
} }
``` ```
The `extensions` array should contain extensions without wildcards or dots (e.g. The `extensions` array should contain extensions without wildcards or dots (e.g.
`'png'` is good but `'.png'` and `'*.png'` are bad). To show all files, use the `'png'` is good but `'.png'` and `'*.png'` are bad). To show all files, use the
`'*'` wildcard (no other wildcard is supported). `'*'` wildcard (no other wildcard is supported).
If a `callback` is passed, the API call will be asynchronous and the result If a `callback` is passed, the API call will be asynchronous and the result
wil be passed via `callback(filenames)` will be passed via `callback(filenames)`
**Note:** On Windows and Linux an open dialog can not be both a file selector **Note:** On Windows and Linux an open dialog can not be both a file selector
and a directory selector, so if you set `properties` to and a directory selector, so if you set `properties` to

View file

@ -12,9 +12,10 @@ Create a new `MenuItem` with the following method:
### new MenuItem(options) ### new MenuItem(options)
* `options` Object * `options` Object
* `click` Function - Callback when the menu item is clicked * `click` Function - Will be called with `click(menuItem, browserWindow)` when
* `selector` String - Call the selector of first responder when clicked (OS the menu item is clicked
X only) * `role` String - Define the action of the menu item, when specified the
`click` property will be ignored
* `type` String - Can be `normal`, `separator`, `submenu`, `checkbox` or * `type` String - Can be `normal`, `separator`, `submenu`, `checkbox` or
`radio` `radio`
* `label` String * `label` String
@ -30,3 +31,29 @@ Create a new `MenuItem` with the following method:
as a reference to this item by the position attribute. as a reference to this item by the position attribute.
* `position` String - This field allows fine-grained definition of the * `position` String - This field allows fine-grained definition of the
specific location within a given menu. specific location within a given menu.
When creating menu items, it is recommended to specify `role` instead of
manually implementing the behavior if there is matching action, so menu can have
best native experience.
The `role` property can have following values:
* `undo`
* `redo`
* `cut`
* `copy`
* `paste`
* `selectall`
* `minimize` - Minimize current window
* `close` - Close current window
On OS X `role` can also have following additional values:
* `about` - Map to the `orderFrontStandardAboutPanel` action
* `hide` - Map to the `hide` action
* `hideothers` - Map to the `hideOtherApplications` action
* `unhide` - Map to the `unhideAllApplications` action
* `front` - Map to the `arrangeInFront` action
* `window` - The submenu is a "Window" menu
* `help` - The submenu is a "Help" menu
* `services` - The submenu is a "Services" menu

View file

@ -35,68 +35,20 @@ window.addEventListener('contextmenu', function (e) {
An example of creating the application menu in the render process with the An example of creating the application menu in the render process with the
simple template API: simple template API:
**Note to Window and Linux users** the `selector` member of each menu item is a ```javascript
Mac-only [Accelerator option](https://github.com/atom/electron/blob/master/docs/api/accelerator.md).
```html
<!-- index.html -->
<script>
var remote = require('remote');
var Menu = remote.require('menu');
var template = [ var template = [
{
label: 'Electron',
submenu: [
{
label: 'About Electron',
selector: 'orderFrontStandardAboutPanel:'
},
{
type: 'separator'
},
{
label: 'Services',
submenu: []
},
{
type: 'separator'
},
{
label: 'Hide Electron',
accelerator: 'CmdOrCtrl+H',
selector: 'hide:'
},
{
label: 'Hide Others',
accelerator: 'CmdOrCtrl+Shift+H',
selector: 'hideOtherApplications:'
},
{
label: 'Show All',
selector: 'unhideAllApplications:'
},
{
type: 'separator'
},
{
label: 'Quit',
accelerator: 'CmdOrCtrl+Q',
selector: 'terminate:'
},
]
},
{ {
label: 'Edit', label: 'Edit',
submenu: [ submenu: [
{ {
label: 'Undo', label: 'Undo',
accelerator: 'CmdOrCtrl+Z', accelerator: 'CmdOrCtrl+Z',
selector: 'undo:' role: 'undo'
}, },
{ {
label: 'Redo', label: 'Redo',
accelerator: 'Shift+CmdOrCtrl+Z', accelerator: 'Shift+CmdOrCtrl+Z',
selector: 'redo:' role: 'redo'
}, },
{ {
type: 'separator' type: 'separator'
@ -104,23 +56,23 @@ var template = [
{ {
label: 'Cut', label: 'Cut',
accelerator: 'CmdOrCtrl+X', accelerator: 'CmdOrCtrl+X',
selector: 'cut:' role: 'cut'
}, },
{ {
label: 'Copy', label: 'Copy',
accelerator: 'CmdOrCtrl+C', accelerator: 'CmdOrCtrl+C',
selector: 'copy:' role: 'copy'
}, },
{ {
label: 'Paste', label: 'Paste',
accelerator: 'CmdOrCtrl+V', accelerator: 'CmdOrCtrl+V',
selector: 'paste:' role: 'paste'
}, },
{ {
label: 'Select All', label: 'Select All',
accelerator: 'CmdOrCtrl+A', accelerator: 'CmdOrCtrl+A',
selector: 'selectAll:' role: 'selectall'
} },
] ]
}, },
{ {
@ -129,47 +81,125 @@ var template = [
{ {
label: 'Reload', label: 'Reload',
accelerator: 'CmdOrCtrl+R', accelerator: 'CmdOrCtrl+R',
click: function() { remote.getCurrentWindow().reload(); } click: function(item, focusedWindow) {
if (focusedWindow)
focusedWindow.reload();
}
}, },
{ {
label: 'Toggle DevTools', label: 'Toggle Full Screen',
accelerator: 'Alt+CmdOrCtrl+I', accelerator: (function() {
click: function() { remote.getCurrentWindow().toggleDevTools(); } if (process.platform == 'darwin')
return 'Ctrl+Command+F';
else
return 'F11';
})(),
click: function(item, focusedWindow) {
if (focusedWindow)
focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
}
},
{
label: 'Toggle Developer Tools',
accelerator: (function() {
if (process.platform == 'darwin')
return 'Alt+Command+I';
else
return 'Ctrl+Shift+I';
})(),
click: function(item, focusedWindow) {
if (focusedWindow)
focusedWindow.toggleDevTools();
}
}, },
] ]
}, },
{ {
label: 'Window', label: 'Window',
role: 'window',
submenu: [ submenu: [
{ {
label: 'Minimize', label: 'Minimize',
accelerator: 'CmdOrCtrl+M', accelerator: 'CmdOrCtrl+M',
selector: 'performMiniaturize:' role: 'minimize'
}, },
{ {
label: 'Close', label: 'Close',
accelerator: 'CmdOrCtrl+W', accelerator: 'CmdOrCtrl+W',
selector: 'performClose:' role: 'close'
},
]
},
{
label: 'Help',
role: 'help',
submenu: [
{
label: 'Learn More',
click: function() { require('shell').openExternal('http://electron.atom.io') }
},
]
},
];
if (process.platform == 'darwin') {
var name = require('app').getName();
template.unshift({
label: name,
submenu: [
{
label: 'About ' + name,
role: 'about'
}, },
{ {
type: 'separator' type: 'separator'
}, },
{ {
label: 'Bring All to Front', label: 'Services',
selector: 'arrangeInFront:' role: 'services',
} submenu: []
},
{
type: 'separator'
},
{
label: 'Hide ' + name,
accelerator: 'Command+H',
role: 'hide'
},
{
label: 'Hide Others',
accelerator: 'Command+Shift+H',
role: 'hideothers:'
},
{
label: 'Show All',
role: 'unhide:'
},
{
type: 'separator'
},
{
label: 'Quit',
accelerator: 'Command+Q',
click: function() { app.quit(); }
},
] ]
}, });
{ // Window menu.
label: 'Help', template[3].submenu.push(
submenu: [] {
} type: 'separator'
]; },
{
label: 'Bring All to Front',
role: 'front'
}
);
}
menu = Menu.buildFromTemplate(template); menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu); Menu.setApplicationMenu(menu);
</script>
``` ```
## Class: Menu ## Class: Menu
@ -242,29 +272,26 @@ Linux, here are some notes on making your app's menu more native-like.
### Standard Menus ### Standard Menus
On OS X there are many system defined standard menus, like the `Services` and On OS X there are many system defined standard menus, like the `Services` and
`Windows` menus. To make your menu a standard menu, you can just set your menu's `Windows` menus. To make your menu a standard menu, you should set your menu's
label to one of following and Electron will recognize them and make them `role` to one of following and Electron will recognize them and make them
become standard menus: become standard menus:
* `Window` * `window`
* `Help` * `help`
* `Services` * `services`
### Standard Menu Item Actions ### Standard Menu Item Actions
OS X has provided standard actions for some menu items (which are called OS X has provided standard actions for some menu items, like `About xxx`,
`selector`s), like `About xxx`, `Hide xxx`, and `Hide Others`. To set the action `Hide xxx`, and `Hide Others`. To set the action of a menu item to a standard
of a menu item to a standard action, you can set the `selector` attribute of the action, you should set the `role` attribute of the menu item.
menu item.
### Main Menu's Name ### Main Menu's Name
On OS X the label of application menu's first item is always your app's name, On OS X the label of application menu's first item is always your app's name,
no matter what label you set. To change it you have to change your app's name no matter what label you set. To change it you have to change your app's name
by modifying your app bundle's `Info.plist` file. See by modifying your app bundle's `Info.plist` file. See [About Information
[About Information Property List Files](https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html) Property List Files][AboutInformationPropertyListFiles] for more information.
for more information.
## Menu Item Position ## Menu Item Position
@ -339,3 +366,5 @@ Menu:
- 2 - 2
- 3 - 3
``` ```
[AboutInformationPropertyListFiles]: https://developer.apple.com/library/ios/documentation/general/Reference/InfoPlistKeyReference/Articles/AboutInformationPropertyListFiles.html