Menu bar is load by the first created Atom Window

This commit is contained in:
probablycorey
2013-08-16 14:23:20 -07:00
committed by Corey Johnson & Nathan Sobo
parent b3582b2632
commit afd1a7419d
7 changed files with 215 additions and 117 deletions

View File

@@ -1,4 +1,16 @@
'body':
'meta-alt-ctrl-s': 'application:run-specs'
'meta-,': 'application:show-settings'
'meta-q': 'application:quit'
'meta-h': 'application:hide'
'meta-H': 'application:hide-other-applications'
'meta-n': 'application:new-file'
'meta-N': 'application:new-window'
'meta-o': 'application:open'
'meta-O': 'application:open-dev'
'meta-m': 'application:minimize'
'alt-meta-ctrl-m': 'application:zoom'
'meta-s': 'core:save'
'meta-S': 'core:save-as'
'enter': 'core:confirm'
@@ -26,12 +38,13 @@
'meta-alt-s': 'window:save-all'
'meta-W': 'window:close'
'meta-r': 'window:reload'
'meta-+': 'window:increase-font-size'
'meta--': 'window:decrease-font-size'
'ctrl-w w': 'window:focus-next-pane'
'ctrl-tab': 'window:focus-next-pane'
'ctrl-meta-f': 'window:toggle-full-screen'
'meta-r': 'window:reload'
'alt-meta-i': 'window:toggle-dev-tools'
'ctrl-|': 'pane:split-right'
'ctrl-w v': 'pane:split-right'
@@ -55,14 +68,6 @@
'alt-meta-w': 'pane:close-other-items'
'meta-P': 'pane:close'
'meta-n': 'new-editor'
'meta-N': 'new-window'
'meta-,': 'open-user-configuration'
'meta-o': 'open'
'meta-O': 'open-dev'
'meta-w': 'core:close'
'alt-meta-i': 'toggle-dev-tools'
'.tool-panel':
'meta-escape': 'tool-panel:unfocus'
'escape': 'core:close'

95
src/app/menu-bar.coffee Normal file
View File

@@ -0,0 +1,95 @@
ipc = require 'ipc'
module.exports =
class MenuBar
@show: ->
atomMenu =
label: 'Atom'
submenu: [
{ label: 'About Atom', command: 'application:about' }
{ label: "Version #{atom.getVersion()}", enabled: false }
{ label: "Install update", command: 'application:install-update', visible: false }
{ type: 'separator' }
{ label: 'Preferences...', command: 'application:show-settings' }
{ label: 'Hide Atom', command: 'application:hide' }
{ label: 'Hide Others', command: 'application:hide-other-applications' }
{ label: 'Show All', command: 'application:unhide-all-applications' }
{ type: 'separator' }
{ label: 'Run Specs', command: 'application:run-specs' }
{ type: 'separator' }
{ label: 'Quit', command: 'application:quit' }
]
fileMenu =
label: 'File'
submenu: [
{ label: 'New Window', command: 'application:new-window' }
{ label: 'New File', command: 'application:new-file' }
{ type: 'separator' }
{ label: 'Open...', command: 'application:open' }
{ label: 'Open In Dev Mode...', command: 'application:open-dev' }
{ type: 'separator' }
{ label: 'Close Window', command: 'window:close' }
]
editMenu =
label: 'Edit'
submenu: [
{ label: 'Undo', command: 'core:undo' }
{ label: 'Redo', command: 'core:redo' }
{ type: 'separator' }
{ label: 'Cut', command: 'core:cut' }
{ label: 'Copy', command: 'core:copy' }
{ label: 'Paste', command: 'core:paste' }
{ label: 'Select All', command: 'core:select-all' }
]
viewMenu =
label: 'View'
submenu: [
{ label: 'Reload', command: 'window:reload' }
{ label: 'Toggle Full Screen', command: 'window:toggle-full-screen' }
{ label: 'Toggle Developer Tools', command: 'window:toggle-dev-tools' }
]
windowMenu =
label: 'Window'
submenu: [
{ label: 'Minimize', command: 'application:minimize' }
{ label: 'Zoom', command: 'application:zoom' }
{ type: 'separator' }
{ label: 'Bring All to Front', command: 'application:bring-all-windows-to-front' }
]
menu = [atomMenu, fileMenu, editMenu, viewMenu, windowMenu]
if atom.isDevMode()
menu.push
label: '\uD83D\uDC80' # Skull emoji
submenu: [ { label: 'In Development Mode', enabled: false } ]
@addKeyBindings(menu)
ipc.sendChannel 'build-menu-bar-from-template', menu
@addKeyBindings: (menuItems) ->
for menuItem in menuItems
if menuItem.command
menuItem.accelerator = @acceleratorForCommand(menuItem.command)
@addKeyBindings(menuItem.submenu) if menuItem.submenu
@acceleratorForCommand: (command) ->
keyBindings = keymap.keyBindingsForCommand(command)
keyBinding = keyBindings[0]
return null unless keyBinding
modifiers = keyBinding.split('-')
key = modifiers.pop()
modifiers.push("Shift") if key != key.toLowerCase()
modifiers = modifiers.map (modifier) ->
modifier.replace(/shift/ig, "Shift")
.replace(/meta/ig, "Command")
.replace(/ctrl/ig, "MacCtrl")
.replace(/alt/ig, "Alt")
keys = modifiers.concat([key.toUpperCase()])
keys.join("+")

View File

@@ -9,6 +9,7 @@ fs = require 'fs'
path = require 'path'
net = require 'net'
url = require 'url'
_ = require 'underscore'
socketPath = '/tmp/atom.sock'
@@ -35,7 +36,6 @@ class AtomApplication
windows: null
menu: null
resourcePath: null
installUpdate: null
version: null
constructor: ({@resourcePath, pathsToOpen, urlsToOpen, @version, test, pidToKillWhenClosed, devMode, newWindow}) ->
@@ -47,9 +47,7 @@ class AtomApplication
@listenForArgumentsFromNewProcess()
@setupJavaScriptArguments()
@buildApplicationMenu()
@handleEvents()
@checkForUpdates()
if test
@@ -63,6 +61,7 @@ class AtomApplication
removeWindow: (window) ->
@windows.splice @windows.indexOf(window), 1
@enableWindowMenuItems(false) if @windows.length == 0
addWindow: (window) ->
@windows.push window
@@ -91,87 +90,9 @@ class AtomApplication
autoUpdater.setAutomaticallyChecksForUpdates true
autoUpdater.checkForUpdatesInBackground()
buildApplicationMenu: (version, continueUpdate) ->
menus = []
menus.push
label: 'Atom'
submenu: [
{ label: 'About Atom', selector: 'orderFrontStandardAboutPanel:' }
{ type: 'separator' }
{ label: 'Preferences...', accelerator: 'Command+,', click: => @sendCommand('window:open-settings') }
{ type: 'separator' }
{ label: 'Hide Atom', accelerator: 'Command+H', selector: 'hide:' }
{ label: 'Hide Others', accelerator: 'Command+Shift+H', selector: 'hideOtherApplications:' }
{ label: 'Show All', selector: 'unhideAllApplications:' }
{ type: 'separator' }
{
label: 'Run Specs'
accelerator: 'Command+MacCtrl+Alt+S'
click: =>
@runSpecs(exitWhenDone: false, resourcePath: global.devResourcePath)
}
{ type: 'separator' }
{ label: 'Quit', accelerator: 'Command+Q', click: -> app.quit() }
]
menus[0].submenu[1..0] =
if version
label: "Update to #{version}"
click: continueUpdate
else
label: "Version #{@version}"
enabled: false
if devMode
menus.push
label: '\uD83D\uDC80' # Skull emoji
submenu: [ { label: 'In Development Mode', enabled: false } ]
menus.push
label: 'File'
submenu: [
{ label: 'New Window', accelerator: 'Command+Shift+N', click: => @openPath() }
{ label: 'Open...', accelerator: 'Command+O', click: => @promptForPath() }
{ label: 'Open In Dev Mode...', accelerator: 'Command+Shift+O', click: => @promptForPath(devMode: true) }
]
menus.push
label: 'Edit'
submenu:[
{ label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' }
{ label: 'Redo', accelerator: 'Command+Shift+Z', selector: 'redo:' }
{ type: 'separator' }
{ label: 'Cut', accelerator: 'Command+X', selector: 'cut:' }
{ label: 'Copy', accelerator: 'Command+C', selector: 'copy:' }
{ label: 'Paste', accelerator: 'Command+V', selector: 'paste:' }
{ label: 'Select All', accelerator: 'Command+A', selector: 'selectAll:' }
]
menus.push
label: 'View'
submenu:[
{ label: 'Reload', accelerator: 'Command+R', click: => BrowserWindow.getFocusedWindow()?.restart() }
{ label: 'Toggle Full Screen', accelerator: 'Command+MacCtrl+F', click: => BrowserWindow.getFocusedWindow()?.setFullScreen(!BrowserWindow.getFocusedWindow().isFullScreen()) }
{ label: 'Toggle Developer Tools', accelerator: 'Alt+Command+I', click: => BrowserWindow.getFocusedWindow()?.toggleDevTools() }
]
menus.push
label: 'Window'
submenu: [
{ label: 'Minimize', accelerator: 'Command+M', selector: 'performMiniaturize:' }
{ label: 'Zoom', accelerator: 'Alt+Command+MacCtrl+M', selector: 'zoom:' }
{ label: 'Close', accelerator: 'Command+W', selector: 'performClose:' }
{ type: 'separator' }
{ label: 'Bring All to Front', selector: 'arrangeInFront:' }
]
@menu = Menu.buildFromTemplate menus
Menu.setApplicationMenu @menu
handleEvents: ->
# Clean the socket file when quit normally.
app.on 'will-quit', =>
fs.unlinkSync socketPath if fs.existsSync(socketPath)
fs.unlinkSync socketPath if fs.existsSync(socketPath) # Clean the socket file when quit normally.
app.on 'open-file', (event, pathToOpen) =>
event.preventDefault()
@@ -181,10 +102,12 @@ class AtomApplication
event.preventDefault()
@openUrl(urlToOpen)
autoUpdater.on 'ready-for-update-on-quit', (event, version, quitAndUpdate) =>
autoUpdater.on 'ready-for-update-on-quit', (event, version, quitAndUpdateCallback) =>
event.preventDefault()
@installUpdate = quitAndUpdate
@buildApplicationMenu version, quitAndUpdate
updateMenuItem = _.find @allMenuItems(), (menuItem) -> menuItem.label == 'Install update'
if updateMenuItem
updateMenuItem.visible = true
updateMenuItem.click = quitAndUpdateCallback
ipc.on 'open', (processId, routingId, pathsToOpen) =>
if pathsToOpen?.length > 0
@@ -192,32 +115,91 @@ class AtomApplication
else
@promptForPath()
ipc.on 'open-window', (processId, routingId, windowSettings) ->
new AtomWindow(windowSettings)
ipc.on 'open-dev', (processId, routingId, pathsToOpen) =>
if pathsToOpen?.length > 0
@openPaths({pathsToOpen, devMode: true})
else
@promptForPath(devMode: true)
ipc.on 'new-window', =>
@openPath()
ipc.on 'install-update', =>
@installUpdate?()
ipc.on 'get-version', (event) =>
event.result = @version
ipc.once 'build-menu-bar-from-template', (processId, routingId, menuTemplate) =>
@buildMenu(menuTemplate)
buildMenu: (menuTemplate) ->
@parseMenuTemplate(menuTemplate)
@menu = Menu.buildFromTemplate(menuTemplate)
Menu.setApplicationMenu(@menu)
@enableWindowMenuItems(true)
allMenuItems: (menu=@menu) ->
return [] unless menu?
menuItems = []
for index, item of menu.items or {}
menuItems.push(item)
menuItems = menuItems.concat(@allMenuItems(item.submenu)) if item.submenu
menuItems
enableWindowMenuItems: (enable) ->
for menuItem in @allMenuItems()
menuItem.enabled = enable if menuItem.metadata?['windowSpecificItem']
parseMenuTemplate: (menuTemplate) ->
menuTemplate.forEach (menuItem) =>
menuItem.metadata = {}
if menuItem.command
menuItem.click = => @sendCommand(menuItem.command)
menuItem.metadata['windowSpecificItem'] = true unless /^application:/.test(menuItem.command)
if menuItem.submenu
@parseMenuTemplate(menuItem.submenu)
sendCommand: (command, args...) ->
for atomWindow in @windows when atomWindow.isFocused()
atomWindow.sendCommand(command, args...)
return if @interceptApplicationCommands(command)
return if @interceptAlternativeWindowCommands(command)
@focusedWindow()?.sendCommand(command, args...)
interceptApplicationCommands: (command) ->
switch command
when 'application:about' then Menu.sendActionToFirstResponder('orderFrontStandardAboutPanel:')
when 'application:run-specs' then @runSpecs(exitWhenDone: false, resourcePath: global.devResourcePath)
when 'application:show-settings' then (@focusedWindow() ? this).openPath("atom://config")
when 'application:quit' then app.quit()
when 'application:hide' then Menu.sendActionToFirstResponder('hide:')
when 'application:hide-other-applications' then Menu.sendActionToFirstResponder('hideOtherApplications:')
when 'application:unhide-all-applications' then Menu.sendActionToFirstResponder('unhideAllApplications:')
when 'application:new-window' then @openPath()
when 'application:new-file' then (@focusedWindow() ? this).openPath()
when 'application:open' then @promptForPath()
when 'application:open-dev' then @promptForPath(devMode: true)
when 'application:minimize' then Menu.sendActionToFirstResponder('performMiniaturize:')
when 'application:zoom' then Menu.sendActionToFirstResponder('zoom:')
when 'application:bring-all-windows-to-front' then Menu.sendActionToFirstResponder('arrangeInFront:')
else
return false
true
interceptAlternativeWindowCommands: (command) ->
return if not @focusedWindow()?.isSpecWindow() and @focusedWindow()?.isWebViewFocused()
switch command
when 'core:undo' then Menu.sendActionToFirstResponder('undo:')
when 'core:redo' then Menu.sendActionToFirstResponder('redo:')
when 'core:copy' then Menu.sendActionToFirstResponder('copy:')
when 'core:cut' then Menu.sendActionToFirstResponder('cut:')
when 'core:paste' then Menu.sendActionToFirstResponder('paste:')
when 'core:select-all' then Menu.sendActionToFirstResponder('selectAll:')
when 'command-panel:find-in-file' then Menu.sendActionToFirstResponder('performTextFinderAction:')
when 'window:reload' then @focusedWindow()?.reload()
when 'window:toggle-dev-tools' then @focusedWindow()?.toggleDevTools()
when 'window:close' then @focusedWindow()?.close()
else return false
return true
windowForPath: (pathToOpen) ->
for atomWindow in @windows
return atomWindow if atomWindow.containsPath(pathToOpen)
focusedWindow: ->
_.find @windows, (atomWindow) -> atomWindow.isFocused()
openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode}) ->
@openPath({pathToOpen, pidToKillWhenClosed, newWindow, devMode}) for pathToOpen in pathsToOpen ? []
@@ -247,6 +229,8 @@ class AtomApplication
console.log("Killing process #{pid} failed: #{error.code}")
delete @pidsToOpenWindows[pid]
openedWindow
openUrl: ({urlToOpen, devMode}) ->
parsedUrl = url.parse(urlToOpen)
if parsedUrl.host is 'session'

View File

@@ -14,15 +14,16 @@ class AtomWindow
contextMenu: null
inspectElementMenuItem: null
loaded: null
isSpec: null
constructor: (settings={}) ->
{resourcePath, pathToOpen, isSpec} = settings
{resourcePath, pathToOpen, @isSpec} = settings
global.atomApplication.addWindow(this)
@setupNodePath(resourcePath)
@createContextMenu()
@browserWindow = new BrowserWindow show: false, title: 'Atom'
@handleEvents(isSpec)
@handleEvents()
loadSettings = _.extend({}, settings)
loadSettings.windowState ?= ''
@@ -70,7 +71,7 @@ class AtomWindow
else
false
handleEvents: (isSpec)->
handleEvents: ->
@browserWindow.on 'destroyed', =>
global.atomApplication.removeWindow(this)
@@ -96,7 +97,7 @@ class AtomWindow
@inspectElementMenuItem.click = => @browserWindow.inspectElement(x, y)
@contextMenu.popup(@browserWindow)
if isSpec
if @isSpec
# Spec window's web view should always have focus
@browserWindow.on 'blur', =>
@browserWindow.focusOnWebView()
@@ -116,6 +117,16 @@ class AtomWindow
sendCommand: (command, args...) ->
ipc.sendChannel @browserWindow.getProcessId(), @browserWindow.getRoutingId(), 'command', command, args...
close: -> @browserWindow.close()
focus: -> @browserWindow.focus()
isFocused: -> @browserWindow.isFocused()
isWebViewFocused: -> @browserWindow.isWebViewFocused()
isSpecWindow: -> @isSpec
reload: -> @browserWindow.restart()
toggleDevTools: -> @browserWindow.toggleDevTools()

View File

@@ -102,7 +102,7 @@ class Keymap
candidateBindingSets = @bindingSetsForNode(currentNode, bindingSetsForFirstKeystroke)
for bindingSet in candidateBindingSets
command = bindingSet.commandForEvent(event)
if command is 'native!'
if command is 'native!' or /^application:/i.test(command)
return true
else if command
continue if @triggerCommandEvent(event, command)

View File

@@ -20,6 +20,7 @@ class WindowEventHandler
@subscribeToCommand $(window), 'window:toggle-full-screen', => atom.toggleFullScreen()
@subscribeToCommand $(window), 'window:close', => window.close()
@subscribeToCommand $(window), 'window:reload', => atom.reload()
@subscribeToCommand $(window), 'window:toggle-dev-tools', => atom.toggleDevTools()
@subscribeToCommand $(document), 'core:focus-next', @focusNext
@subscribeToCommand $(document), 'core:focus-previous', @focusPrevious

View File

@@ -5,6 +5,7 @@ $ = require 'jquery'
less = require 'less'
remote = require 'remote'
WindowEventHandler = require 'window-event-handler'
MenuBar = require 'menu-bar'
require 'jquery-extensions'
require 'underscore-extensions'
require 'space-pen-extensions'
@@ -33,9 +34,9 @@ window.setUpEnvironment = (windowMode) ->
window.syntax = deserialize(atom.getWindowState('syntax')) ? new Syntax
window.pasteboard = new Pasteboard
window.keymap = new Keymap()
keymap.bindDefaultKeys()
requireStylesheet 'atom'
if nativeStylesheetPath = fsUtils.resolveOnLoadPath(process.platform, ['css', 'less'])
@@ -56,6 +57,7 @@ window.startEditorWindow = ->
atom.activatePackages()
keymap.loadUserKeymaps()
atom.requireUserInitScript()
MenuBar.show()
$(window).on 'unload', -> unloadEditorWindow(); false
atom.show()
atom.focus()