mirror of https://github.com/atom/atom.git
249 lines
7.7 KiB
JavaScript
249 lines
7.7 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) {
|
|
const keystrokes = keystrokesByCommand[item.command];
|
|
if (keystrokes && keystrokes.length > 0) {
|
|
const keystroke = keystrokes[0];
|
|
// Electron does not support multi-keystroke accelerators. Therefore,
|
|
// when the command maps to a multi-stroke key binding, show the
|
|
// keystrokes next to the item's label.
|
|
if (keystroke.includes(' ')) {
|
|
item.label += ` [${_.humanizeKeystroke(keystroke)}]`;
|
|
} else {
|
|
item.accelerator = MenuHelpers.acceleratorForKeystroke(keystroke);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
};
|