Files
atom/src/main-process/application-menu.js

226 lines
7.5 KiB
JavaScript

const {app, Menu} = require('electron')
const _ = require('underscore-plus')
const MenuHelpers = require('../menu-helpers')
// Used to manage the global application menu.
//
// It's created by {AtomApplication} upon instantiation and used to add, remove
// and maintain the state of all menu items.
module.exports =
class ApplicationMenu {
constructor (version, autoUpdateManager) {
this.version = version
this.autoUpdateManager = autoUpdateManager
this.windowTemplates = new WeakMap()
this.setActiveTemplate(this.getDefaultTemplate())
this.autoUpdateManager.on('state-changed', state => this.showUpdateMenuItem(state))
}
// Public: Updates the entire menu with the given keybindings.
//
// window - The BrowserWindow this menu template is associated with.
// template - The Object which describes the menu to display.
// keystrokesByCommand - An Object where the keys are commands and the values
// are Arrays containing the keystroke.
update (window, template, keystrokesByCommand) {
this.translateTemplate(template, keystrokesByCommand)
this.substituteVersion(template)
this.windowTemplates.set(window, template)
if (window === this.lastFocusedWindow) return this.setActiveTemplate(template)
}
setActiveTemplate (template) {
if (!_.isEqual(template, this.activeTemplate)) {
this.activeTemplate = template
this.menu = Menu.buildFromTemplate(_.deepClone(template))
Menu.setApplicationMenu(this.menu)
}
return this.showUpdateMenuItem(this.autoUpdateManager.getState())
}
// Register a BrowserWindow with this application menu.
addWindow (window) {
if (this.lastFocusedWindow == null) this.lastFocusedWindow = window
const focusHandler = () => {
this.lastFocusedWindow = window
const template = this.windowTemplates.get(window)
if (template) this.setActiveTemplate(template)
}
window.on('focus', focusHandler)
window.once('closed', () => {
if (window === this.lastFocusedWindow) this.lastFocusedWindow = null
this.windowTemplates.delete(window)
window.removeListener('focus', focusHandler)
})
this.enableWindowSpecificItems(true)
}
// Flattens the given menu and submenu items into an single Array.
//
// menu - A complete menu configuration object for atom-shell's menu API.
//
// Returns an Array of native menu items.
flattenMenuItems (menu) {
const object = menu.items || {}
let items = []
for (let index in object) {
const item = object[index]
items.push(item)
if (item.submenu) items = items.concat(this.flattenMenuItems(item.submenu))
}
return items
}
// Flattens the given menu template into an single Array.
//
// template - An object describing the menu item.
//
// Returns an Array of native menu items.
flattenMenuTemplate (template) {
let items = []
for (let item of template) {
items.push(item)
if (item.submenu) items = items.concat(this.flattenMenuTemplate(item.submenu))
}
return items
}
// Public: Used to make all window related menu items are active.
//
// enable - If true enables all window specific items, if false disables all
// window specific items.
enableWindowSpecificItems (enable) {
for (let item of this.flattenMenuItems(this.menu)) {
if (item.metadata && item.metadata.windowSpecific) item.enabled = enable
}
}
// Replaces VERSION with the current version.
substituteVersion (template) {
let item = this.flattenMenuTemplate(template).find(({label}) => label === 'VERSION')
if (item) item.label = `Version ${this.version}`
}
// Sets the proper visible state the update menu items
showUpdateMenuItem (state) {
const items = this.flattenMenuItems(this.menu)
const checkForUpdateItem = items.find(({label}) => label === 'Check for Update')
const checkingForUpdateItem = items.find(({label}) => label === 'Checking for Update')
const downloadingUpdateItem = items.find(({label}) => label === 'Downloading Update')
const installUpdateItem = items.find(({label}) => label === 'Restart and Install Update')
if (!checkForUpdateItem || !checkingForUpdateItem ||
!downloadingUpdateItem || !installUpdateItem) return
checkForUpdateItem.visible = false
checkingForUpdateItem.visible = false
downloadingUpdateItem.visible = false
installUpdateItem.visible = false
switch (state) {
case 'idle':
case 'error':
case 'no-update-available':
checkForUpdateItem.visible = true
break
case 'checking':
checkingForUpdateItem.visible = true
break
case 'downloading':
downloadingUpdateItem.visible = true
break
case 'update-available':
installUpdateItem.visible = true
break
}
}
// Default list of menu items.
//
// Returns an Array of menu item Objects.
getDefaultTemplate () {
return [{
label: 'Atom',
submenu: [
{
label: 'Check for Update',
metadata: {autoUpdate: true}
},
{
label: 'Reload',
accelerator: 'Command+R',
click: () => {
const window = this.focusedWindow()
if (window) window.reload()
}
},
{
label: 'Close Window',
accelerator: 'Command+Shift+W',
click: () => {
const window = this.focusedWindow()
if (window) window.close()
}
},
{
label: 'Toggle Dev Tools',
accelerator: 'Command+Alt+I',
click: () => {
const window = this.focusedWindow()
if (window) window.toggleDevTools()
}
},
{
label: 'Quit',
accelerator: 'Command+Q',
click: () => app.quit()
}
]
}]
}
focusedWindow () {
return global.atomApplication.getAllWindows().find(window => window.isFocused())
}
// Combines a menu template with the appropriate keystroke.
//
// template - An Object conforming to atom-shell's menu api but lacking
// accelerator and click properties.
// keystrokesByCommand - An Object where the keys are commands and the values
// are Arrays containing the keystroke.
//
// Returns a complete menu configuration object for atom-shell's menu API.
translateTemplate (template, keystrokesByCommand) {
template.forEach(item => {
if (item.metadata == null) item.metadata = {}
if (item.command) {
item.accelerator = this.acceleratorForCommand(item.command, keystrokesByCommand)
item.click = () => global.atomApplication.sendCommand(item.command, item.commandDetail)
if (!/^application:/.test(item.command)) {
item.metadata.windowSpecific = true
}
}
if (item.submenu) this.translateTemplate(item.submenu, keystrokesByCommand)
})
return template
}
// Determine the accelerator for a given command.
//
// command - The name of the command.
// keystrokesByCommand - An Object where the keys are commands and the values
// are Arrays containing the keystroke.
//
// Returns a String containing the keystroke in a format that can be interpreted
// by Electron to provide nice icons where available.
acceleratorForCommand (command, keystrokesByCommand) {
const firstKeystroke = keystrokesByCommand[command] && keystrokesByCommand[command][0]
return MenuHelpers.acceleratorForKeystroke(firstKeystroke)
}
}