electron/atom/browser/api/lib/menu.coffee
2014-05-25 16:37:45 +08:00

130 lines
4.3 KiB
CoffeeScript

BrowserWindow = require 'browser-window'
EventEmitter = require('events').EventEmitter
MenuItem = require 'menu-item'
v8Util = process.atomBinding 'v8_util'
bindings = process.atomBinding 'menu'
# Automatically generated radio menu item's group id.
nextGroupId = 0
# Search between seperators to find a radio menu item and return its group id,
# otherwise generate a group id.
generateGroupId = (items, pos) ->
if pos > 0
for i in [pos - 1..0]
item = items[i]
return item.groupId if item.type is 'radio'
break if item.type is 'separator'
else if pos < items.length
for i in [pos..items.length - 1]
item = items[i]
return item.groupId if item.type is 'radio'
break if item.type is 'separator'
++nextGroupId
Menu = bindings.Menu
Menu::__proto__ = EventEmitter.prototype
Menu::popup = (window) ->
throw new TypeError('Invalid window') unless window?.constructor is BrowserWindow
@_popup window
Menu::append = (item) ->
@insert @getItemCount(), item
Menu::insert = (pos, item) ->
throw new TypeError('Invalid item') unless item?.constructor is MenuItem
# Create delegate.
unless @delegate?
@commandsMap = {}
@groupsMap = {}
@items = []
@delegate =
isCommandIdChecked: (commandId) => @commandsMap[commandId]?.checked
isCommandIdEnabled: (commandId) => @commandsMap[commandId]?.enabled
isCommandIdVisible: (commandId) => @commandsMap[commandId]?.visible
getAcceleratorForCommandId: (commandId) => @commandsMap[commandId]?.accelerator
executeCommand: (commandId) =>
activeItem = @commandsMap[commandId]
# Manually flip the checked flags when clicked.
if activeItem?
if activeItem.type in ['checkbox', 'radio']
activeItem.checked = !activeItem.checked
activeItem.click()
menuWillShow: =>
# Make sure radio groups have at least one menu item seleted.
for id, group of @groupsMap
checked = false
for radioItem in group when radioItem.checked
checked = true
break
v8Util.setHiddenValue group[0], 'checked', true unless checked
switch item.type
when 'normal' then @insertItem pos, item.commandId, item.label
when 'checkbox' then @insertCheckItem pos, item.commandId, item.label
when 'separator' then @insertSeparator pos
when 'submenu' then @insertSubMenu pos, item.commandId, item.label, item.submenu
when 'radio'
# Grouping radio menu items.
item.groupId = generateGroupId(@items, pos)
@groupsMap[item.groupId] ?= []
@groupsMap[item.groupId].push item
# Setting a radio menu item should flip other items in the group.
v8Util.setHiddenValue item, 'checked', item.checked
Object.defineProperty item, 'checked',
enumerable: true
get: -> v8Util.getHiddenValue item, 'checked'
set: (val) =>
for otherItem in @groupsMap[item.groupId]
v8Util.setHiddenValue otherItem, 'checked', false
v8Util.setHiddenValue item, 'checked', true
@insertRadioItem pos, item.commandId, item.label, item.groupId
@setSublabel pos, item.sublabel if item.sublabel?
# Remember the items.
@items.splice pos, 0, item
@commandsMap[item.commandId] = item
applicationMenu = null
Menu.setApplicationMenu = (menu) ->
throw new TypeError('Invalid menu') unless menu?.constructor is Menu
applicationMenu = menu # Keep a reference.
if process.platform is 'darwin'
# Force menuWillShow to be called
menuWillShow = (menu) ->
menu.delegate?.menuWillShow()
menuWillShow item.submenu for item in menu.items when item.submenu?
menuWillShow menu
bindings.setApplicationMenu menu
else
windows = BrowserWindow.getAllWindows()
w.setMenu menu for w in windows
Menu.getApplicationMenu = -> applicationMenu
Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder
Menu.buildFromTemplate = (template) ->
throw new TypeError('Invalid template for Menu') unless Array.isArray template
menu = new Menu
for item in template
throw new TypeError('Invalid template for MenuItem') unless typeof item is 'object'
item.submenu = Menu.buildFromTemplate item.submenu if item.submenu?
menuItem = new MenuItem(item)
menuItem[key] = value for key, value of item when not menuItem[key]?
menu.append menuItem
menu
module.exports = Menu