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) } }