diff --git a/lib/browser/api/menu.js b/lib/browser/api/menu.js index 75b5a31e3b0..71ac7f5241d 100644 --- a/lib/browser/api/menu.js +++ b/lib/browser/api/menu.js @@ -5,88 +5,23 @@ const {EventEmitter} = require('events') const v8Util = process.atomBinding('v8_util') const bindings = process.atomBinding('menu') -// Automatically generated radio menu item's group id. -let nextGroupId = 0 - -/* Search between seperators to find a radio menu item and return its group id */ -const generateGroupId = function(items, pos) { - let i, item - if (pos > 0) { - let asc, start - for (start = pos - 1, i = start, asc = start <= 0; asc ? i <= 0 : i >= 0; asc ? i++ : i--) { - item = items[i] - if (item.type === 'radio') { return item.groupId } - if (item.type === 'separator') { break } - } - } else if (pos < items.length) { - let asc1, end - for (i = pos, end = items.length - 1, asc1 = pos <= end; asc1 ? i <= end : i >= end; asc1 ? i++ : i--) { - item = items[i] - if (item.type === 'radio') { return item.groupId } - if (item.type === 'separator') { break } - } - } - return ++nextGroupId -} - -/* Returns the index of item according to |id|. */ -const indexOfItemById = function(items, id) { - for (let i = 0; i < items.length; i++) { - const item = items[i] - if (item.id === id) return i - } - return -1 -} - -/* Returns the index of where to insert the item according to |position|. */ -const indexToInsertByPosition = function(items, position) { - if (!position) { return items.length } - - const [query, id] = Array.from(position.split('=')) - let insertIndex = indexOfItemById(items, id) - if ((insertIndex === -1) && (query !== 'endof')) { - console.warn(`Item with id '${id}' is not found`) - return items.length - } - - switch (query) { - case 'after': - insertIndex++ - break - case 'endof': - /* If the |id| doesn't exist, then create a new group with the |id|. */ - if (insertIndex === -1) { - items.push({id, type: 'separator'}) - insertIndex = items.length - 1 - } - - /* Find the end of the group. */ - insertIndex++ - while ((insertIndex < items.length) && (items[insertIndex].type !== 'separator')) { - insertIndex++ - } - break - } - - return insertIndex -} - const { Menu } = bindings Menu.prototype.__proto__ = EventEmitter.prototype -Menu.prototype._init = function() { +Menu.prototype._init = function () { this.commandsMap = {} this.groupsMap = {} this.items = [] return this.delegate = { - isCommandIdChecked: commandId => (this.commandsMap[commandId] != null ? this.commandsMap[commandId].checked : undefined), - isCommandIdEnabled: commandId => (this.commandsMap[commandId] != null ? this.commandsMap[commandId].enabled : undefined), - isCommandIdVisible: commandId => (this.commandsMap[commandId] != null ? this.commandsMap[commandId].visible : undefined), - getAcceleratorForCommandId: commandId => (this.commandsMap[commandId] != null ? this.commandsMap[commandId].accelerator : undefined), - getIconForCommandId: commandId => (this.commandsMap[commandId] != null ? this.commandsMap[commandId].icon : undefined), - executeCommand: commandId => { - return (this.commandsMap[commandId] != null ? this.commandsMap[commandId].click(BrowserWindow.getFocusedWindow()) : undefined) + isCommandIdChecked: id => (this.commandsMap[id] ? this.commandsMap[id].checked : undefined), + isCommandIdEnabled: id => (this.commandsMap[id] ? this.commandsMap[id].enabled : undefined), + isCommandIdVisible: id => (this.commandsMap[id] ? this.commandsMap[id].visible : undefined), + getAcceleratorForCommandId: id => (this.commandsMap[id] ? this.commandsMap[id].accelerator : undefined), + getIconForCommandId: id => (this.commandsMap[id] ? this.commandsMap[id].icon : undefined), + executeCommand: (id) => { + const command = this.commandsMap[id] + return command ? this.commandsMap[id].click(BrowserWindow.getFocusedWindow()) : undefined }, menuWillShow: () => { for (let id in this.groupsMap) { @@ -104,25 +39,41 @@ Menu.prototype._init = function() { } } -Menu.prototype.popup = function(window, x, y) { - if ((window != null ? window.constructor : undefined) !== BrowserWindow) { - /* Shift. */ +Menu.prototype.popup = function (window, x, y, positioningItem) { + let asyncPopup + + // menu.popup(x, y, positioningItem) + if (window != null && (typeof window !== 'object' || window.constructor !== BrowserWindow)) { + // Shift. + positioningItem = y y = x x = window - window = BrowserWindow.getFocusedWindow() + window = null } - if (x !== null && y !== null) { - return this._popupAt(window, x, y) - } else { - return this._popup(window) + + // menu.popup(window, {}) + if (x != null && typeof x === 'object') { + const options = x + x = options.x + y = options.y + positioningItem = options.positioningItem + asyncPopup = options.async } + + if (window == null) window = BrowserWindow.getFocusedWindow() + if (typeof x !== 'number') x = -1 + if (typeof y !== 'number') y = -1 + if (typeof positioningItem !== 'number') positioningItem = -1 + if (typeof asyncPopup !== 'boolean') asyncPopup = false + + this.popupAt(window, x, y, positioningItem, asyncPopup) } -Menu.prototype.append = function(item) { +Menu.prototype.append = function (item) { return this.insert(this.getItemCount(), item) } -Menu.prototype.insert = function(pos, item) { +Menu.prototype.insert = function (pos, item) { if ((item != null ? item.constructor : undefined) !== MenuItem) { throw new TypeError('Invalid item') } @@ -176,33 +127,39 @@ Menu.prototype.insert = function(pos, item) { } /* Force menuWillShow to be called */ -Menu.prototype._callMenuWillShow = function() { +Menu.prototype._callMenuWillShow = function () { if (this.delegate != null) this.delegate.menuWillShow() - return (() => { - const result = [] - for (let item of Array.from(this.items)) { - if (item.submenu != null) { - result.push(item.submenu._callMenuWillShow()) - } - } - return result - })() + this.items.forEach(item => { + if (item.submenu != null) item.submenu._callMenuWillShow() + }) } -let applicationMenu = null -Menu.setApplicationMenu = function(menu) { - if ((menu !== null) && (menu.constructor !== Menu)) { throw new TypeError('Invalid menu') } - /* Keep a reference. */ - applicationMenu = menu +Menu.prototype.getMenuItemById = function (id) { + const items = this.items + let found = items.find(item => item.id === id) || null + for (let i = 0, length = items.length; !found && i < length; i++) { + if (items[i].submenu) { + found = items[i].submenu.getMenuItemById(id) + } + } + return found +} + +Menu.setApplicationMenu = function (menu) { + if (menu !== null && menu.constructor !== Menu) { + throw new TypeError('Invalid menu') + } + + applicationMenu = menu if (process.platform === 'darwin') { - if (menu === null) { return } + if (menu === null) return menu._callMenuWillShow() return bindings.setApplicationMenu(menu) } else { const windows = BrowserWindow.getAllWindows() - return Array.from(windows).map((w) => w.setMenu(menu)) + return windows.map((w) => w.setMenu(menu)) } } @@ -210,38 +167,103 @@ Menu.getApplicationMenu = () => applicationMenu Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder -Menu.buildFromTemplate = function(template) { - if (!Array.isArray(template)) { throw new TypeError('Invalid template for Menu') } - - const positionedTemplate = [] - let insertIndex = 0 - - for (var item of Array.from(template)) { - if (item.position) { - insertIndex = indexToInsertByPosition(positionedTemplate, item.position) - } else { - /* If no |position| is specified, insert after last item. */ - insertIndex++ - } - positionedTemplate.splice(insertIndex, 0, item) +// build menu from a template +Menu.buildFromTemplate = function (template) { + if (!(template instanceof Array)) { + throw new TypeError('Invalid template for Menu') } const menu = new Menu + const positioned = [] + let idx = 0 - for (item of Array.from(positionedTemplate)) { - if (typeof item !== 'object') { throw new TypeError('Invalid template for MenuItem') } + template.forEach(item => { + idx = (item.position) ? indexToInsertByPosition(positioned, item.position) : idx++ + positioned.splice(idx, 0, item) + }) - const menuItem = new MenuItem(item) - for (let key in item) { - const value = item[key] - if (menuItem[key] == null) { - menuItem[key] = value - } + positioned.forEach((item) => { + if (typeof item !== 'object') { + throw new TypeError('Invalid template for MenuItem') } - menu.append(menuItem) - } + menu.append(new MenuItem(item)) + }) return menu } +// Automatically generated radio menu item's group id. +let nextGroupId = 0 +let applicationMenu = null + +/* Search between seperators to find a radio menu item and return its group id */ +const generateGroupId = function(items, pos) { + let i, item + if (pos > 0) { + let asc, start + for (start = pos - 1, i = start, asc = start <= 0; asc ? i <= 0 : i >= 0; asc ? i++ : i--) { + item = items[i] + if (item.type === 'radio') { return item.groupId } + if (item.type === 'separator') { break } + } + } else if (pos < items.length) { + let asc1, end + for (i = pos, end = items.length - 1, asc1 = pos <= end; asc1 ? i <= end : i >= end; asc1 ? i++ : i--) { + item = items[i] + if (item.type === 'radio') { return item.groupId } + if (item.type === 'separator') { break } + } + } + return ++nextGroupId +} + +/* Returns the index of item according to |id|. */ +const indexOfItemById = function (items, id) { + for (let i = 0; i < items.length; i++) { + const item = items[i] + if (item.id === id) return i + } + return -1 +} + +/* Returns the index of where to insert the item according to |position|. */ +function indexToInsertByPosition (items, position) { + if (!position) return items.length + const [query, id] = position.split('=') + let idx = indexOfItemById(items, id) + + if (idx === -1 && query !== 'endof') { + console.warn(`Item with id '${id}' is not found`) + return items.length + } + + const queries = { + 'after': () => idx++, + 'endof': () => { + if (idx === -1) { + items.push({id, type: 'separator'}) + idx = items.length - 1 + } + + idx++ + while (idx < items.length && items[idx].type !== 'separator') { + idx++ + } + } + } + + queries[query]() + return idx +} + +// WIP, need to figure out how to pass current menu group to this method +function isOuterSeparator (current, item) { + current.filter(item => { !item.visible }) + const idx = current.indexof(item) + if (idx === 0 || idx === current.length) { + return true + } + return false +} + module.exports = Menu \ No newline at end of file