Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement menu API on Windows #105

Merged
merged 18 commits into from Oct 7, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions browser/api/atom_api_menu.cc
Expand Up @@ -351,6 +351,10 @@ void Menu::Initialize(v8::Handle<v8::Object> target) {

NODE_SET_PROTOTYPE_METHOD(t, "popup", Popup);

#if defined(OS_WIN)
NODE_SET_PROTOTYPE_METHOD(t, "attachToWindow", AttachToWindow);
#endif

target->Set(v8::String::NewSymbol("Menu"), t->GetFunction());

#if defined(OS_MACOSX)
Expand Down
4 changes: 3 additions & 1 deletion browser/api/atom_api_menu.h
Expand Up @@ -68,7 +68,9 @@ class Menu : public EventEmitter,

static v8::Handle<v8::Value> Popup(const v8::Arguments &args);

#if defined(OS_MACOSX)
#if defined(OS_WIN)
static v8::Handle<v8::Value> AttachToWindow(const v8::Arguments &args);
#elif defined(OS_MACOSX)
static v8::Handle<v8::Value> SetApplicationMenu(const v8::Arguments &args);
static v8::Handle<v8::Value> SendActionToFirstResponder(
const v8::Arguments &args);
Expand Down
23 changes: 22 additions & 1 deletion browser/api/atom_api_menu_win.cc
Expand Up @@ -4,8 +4,11 @@

#include "browser/api/atom_api_menu_win.h"

#include "browser/native_window_win.h"
#include "browser/ui/win/menu_2.h"
#include "common/v8_conversions.h"
#include "ui/gfx/point.h"
#include "ui/gfx/screen.h"

namespace atom {

Expand All @@ -19,8 +22,26 @@ MenuWin::~MenuWin() {
}

void MenuWin::Popup(NativeWindow* native_window) {
gfx::Point cursor = gfx::Screen::GetNativeScreen()->GetCursorScreenPoint();
menu_.reset(new atom::Menu2(model_.get()));
menu_->RunContextMenuAt(gfx::Point(0, 0));
menu_->RunContextMenuAt(cursor);
}

// static
v8::Handle<v8::Value> Menu::AttachToWindow(const v8::Arguments& args) {
v8::HandleScope scope;

Menu* self = ObjectWrap::Unwrap<Menu>(args.This());
if (self == NULL)
return node::ThrowError("Menu is already destroyed");

NativeWindow* native_window;
if (!FromV8Arguments(args, &native_window))
return node::ThrowTypeError("Bad argument");

static_cast<NativeWindowWin*>(native_window)->SetMenu(self->model_.get());

return v8::Undefined();
}

// static
Expand Down
3 changes: 3 additions & 0 deletions browser/api/atom_api_window.cc
Expand Up @@ -101,6 +101,9 @@ v8::Handle<v8::Value> Window::New(const v8::Arguments &args) {

new Window(args.This(), static_cast<base::DictionaryValue*>(options.get()));

// Give js code a chance to do initialization.
node::MakeCallback(args.This(), "_init", 0, NULL);

return args.This();
}

Expand Down
9 changes: 9 additions & 0 deletions browser/api/lib/app.coffee
Expand Up @@ -9,6 +9,15 @@ app = new Application
app.getHomeDir = ->
process.env[if process.platform is 'win32' then 'USERPROFILE' else 'HOME']

app.getBrowserWindows = ->
require('../../atom/objects-registry.js').getAllWindows()

app.setApplicationMenu = (menu) ->
require('menu').setApplicationMenu menu

app.getApplicationMenu = ->
require('menu').getApplicationMenu()

app.commandLine =
appendSwitch: bindings.appendSwitch,
appendArgument: bindings.appendArgument
Expand Down
20 changes: 17 additions & 3 deletions browser/api/lib/browser-window.coffee
@@ -1,10 +1,16 @@
EventEmitter = require('events').EventEmitter
app = require 'app'
v8Util = process.atomBinding 'v8_util'
objectsRegistry = require '../../atom/objects-registry.js'

BrowserWindow = process.atomBinding('window').BrowserWindow
BrowserWindow::__proto__ = EventEmitter.prototype

BrowserWindow::_init = ->
# Simulate the application menu on platforms other than OS X.
if process.platform isnt 'darwin'
menu = app.getApplicationMenu()
@setMenu menu if menu?

BrowserWindow::toggleDevTools = ->
opened = v8Util.getHiddenValue this, 'devtoolsOpened'
if opened
Expand All @@ -17,12 +23,20 @@ BrowserWindow::toggleDevTools = ->
BrowserWindow::restart = ->
@loadUrl(@getUrl())

BrowserWindow::setMenu = (menu) ->
throw new Error('BrowserWindow.setMenu is only available on Windows') unless process.platform is 'win32'

throw new TypeError('Invalid menu') unless menu?.constructor?.name is 'Menu'

@menu = menu # Keep a reference of menu in case of GC.
@menu.attachToWindow this

BrowserWindow.getFocusedWindow = ->
windows = objectsRegistry.getAllWindows()
windows = app.getBrowserWindows()
return window for window in windows when window.isFocused()

BrowserWindow.fromProcessIdAndRoutingId = (processId, routingId) ->
windows = objectsRegistry.getAllWindows()
windows = app.getBrowserWindows()
return window for window in windows when window.getProcessId() == processId and
window.getRoutingId() == routingId

Expand Down
4 changes: 3 additions & 1 deletion browser/api/lib/menu-item.coffee
@@ -1,3 +1,5 @@
BrowserWindow = require 'browser-window'

nextCommandId = 0

class MenuItem
Expand Down Expand Up @@ -26,7 +28,7 @@ class MenuItem
@commandId = ++nextCommandId
@click = =>
if typeof click is 'function'
click.apply this, arguments
click this, BrowserWindow.getFocusedWindow()
else if typeof @selector is 'string'
Menu.sendActionToFirstResponder @selector

Expand Down
14 changes: 12 additions & 2 deletions browser/api/lib/menu.coffee
Expand Up @@ -3,6 +3,7 @@ EventEmitter = require('events').EventEmitter
IDWeakMap = require 'id-weak-map'
MenuItem = require 'menu-item'

app = require 'app'
bindings = process.atomBinding 'menu'

Menu = bindings.Menu
Expand Down Expand Up @@ -39,13 +40,22 @@ Menu::insert = (pos, item) ->
getAcceleratorForCommandId: (commandId) => @commandsMap[commandId]?.accelerator
executeCommand: (commandId) =>
activeItem = @commandsMap[commandId]
activeItem.click(activeItem) if activeItem?
activeItem.click() if activeItem?
@items.splice pos, 0, item
@commandsMap[item.commandId] = item

applicationMenu = null
Menu.setApplicationMenu = (menu) ->
throw new TypeError('Invalid menu') unless menu?.constructor is Menu
bindings.setApplicationMenu menu
applicationMenu = menu # Keep a reference.

if process.platform is 'darwin'
bindings.setApplicationMenu menu
else
windows = app.getBrowserWindows()
w.setMenu menu for w in windows

Menu.getApplicationMenu = -> applicationMenu

Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder

Expand Down
99 changes: 56 additions & 43 deletions browser/atom/objects-registry.coffee
@@ -1,7 +1,9 @@
BrowserWindow = require 'browser-window'
EventEmitter = require('events').EventEmitter
IDWeakMap = require 'id-weak-map'
v8Util = process.atomBinding 'v8_util'

# Class to reference all objects.
class ObjectsStore
@stores = {}

Expand Down Expand Up @@ -37,46 +39,57 @@ class ObjectsStore
key = "#{processId}_#{routingId}"
delete @stores[key]

# Objects in weak map will be not referenced (so we won't leak memory), and
# every object created in browser will have a unique id in weak map.
objectsWeakMap = new IDWeakMap
objectsWeakMap.add = (obj) ->
id = IDWeakMap::add.call this, obj
v8Util.setHiddenValue obj, 'atomId', id
id

windowsWeakMap = new IDWeakMap

process.on 'ATOM_BROWSER_INTERNAL_NEW', (obj) ->
# Remember all windows.
if obj.constructor is BrowserWindow
id = windowsWeakMap.add obj
obj.on 'destroyed', ->
windowsWeakMap.remove id

exports.add = (processId, routingId, obj) ->
# Some native objects may already been added to objectsWeakMap, be care not
# to add it twice.
objectsWeakMap.add obj unless v8Util.getHiddenValue obj, 'atomId'
id = v8Util.getHiddenValue obj, 'atomId'

# Store and reference the object, then return the storeId which points to
# where the object is stored. The caller can later dereference the object
# with the storeId.
store = ObjectsStore.forRenderView processId, routingId
storeId = store.add obj

[id, storeId]

exports.get = (id) ->
objectsWeakMap.get id

exports.getAllWindows = () ->
keys = windowsWeakMap.keys()
windowsWeakMap.get key for key in keys

exports.remove = (processId, routingId, storeId) ->
ObjectsStore.forRenderView(processId, routingId).remove storeId

exports.clear = (processId, routingId) ->
ObjectsStore.releaseForRenderView processId, routingId
class ObjectsRegistry extends EventEmitter
constructor: ->
# Objects in weak map will be not referenced (so we won't leak memory), and
# every object created in browser will have a unique id in weak map.
@objectsWeakMap = new IDWeakMap
@objectsWeakMap.add = (obj) ->
id = IDWeakMap::add.call this, obj
v8Util.setHiddenValue obj, 'atomId', id
id

# Remember all windows in the weak map.
@windowsWeakMap = new IDWeakMap
process.on 'ATOM_BROWSER_INTERNAL_NEW', (obj) =>
if obj.constructor is BrowserWindow
id = @windowsWeakMap.add obj
obj.on 'destroyed', => @windowsWeakMap.remove id

# Register a new object, the object would be kept referenced until you release
# it explicitly.
add: (processId, routingId, obj) ->
# Some native objects may already been added to objectsWeakMap, be care not
# to add it twice.
@objectsWeakMap.add obj unless v8Util.getHiddenValue obj, 'atomId'
id = v8Util.getHiddenValue obj, 'atomId'

# Store and reference the object, then return the storeId which points to
# where the object is stored. The caller can later dereference the object
# with the storeId.
# We use a difference key because the same object can be referenced for
# multiple times by the same renderer view.
store = ObjectsStore.forRenderView processId, routingId
storeId = store.add obj

[id, storeId]

# Get an object according to its id.
get: (id) ->
@objectsWeakMap.get id

# Remove an object according to its storeId.
remove: (processId, routingId, storeId) ->
ObjectsStore.forRenderView(processId, routingId).remove storeId

# Clear all references to objects from renderer view.
clear: (processId, routingId) ->
@emit "release-renderer-view-#{processId}-#{routingId}"
ObjectsStore.releaseForRenderView processId, routingId

# Return an array of all browser windows.
getAllWindows: ->
keys = @windowsWeakMap.keys()
@windowsWeakMap.get key for key in keys

module.exports = new ObjectsRegistry
6 changes: 6 additions & 0 deletions browser/atom/rpc-server.coffee
Expand Up @@ -52,9 +52,15 @@ unwrapArgs = (processId, routingId, args) ->
returnValue = metaToValue meta.value
-> returnValue
when 'function'
rendererReleased = false
objectsRegistry.once "release-renderer-view-#{processId}-#{routingId}", ->
rendererReleased = true

ret = ->
throw new Error('Calling a callback of released renderer view') if rendererReleased
ipc.sendChannel processId, routingId, 'ATOM_RENDERER_CALLBACK', meta.id, valueToMeta(processId, routingId, arguments)
v8Util.setDestructor ret, ->
return if rendererReleased
ipc.sendChannel processId, routingId, 'ATOM_RENDERER_RELEASE_CALLBACK', meta.id
ret
else throw new TypeError("Unknown type: #{meta.type}")
Expand Down