Merge pull request #105 from atom/windows-menu
Implement menu API on Windows, fixes #75.
This commit is contained in:
commit
6a712d4db4
21 changed files with 530 additions and 200 deletions
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
BrowserWindow = require 'browser-window'
|
||||
|
||||
nextCommandId = 0
|
||||
|
||||
class MenuItem
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
|
|
@ -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 = {}
|
||||
|
||||
|
@ -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
|
||||
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
|
||||
|
||||
windowsWeakMap = new IDWeakMap
|
||||
# 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
|
||||
|
||||
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
|
||||
# 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'
|
||||
|
||||
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.
|
||||
# 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
|
||||
|
||||
# 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]
|
||||
|
||||
[id, storeId]
|
||||
# Get an object according to its id.
|
||||
get: (id) ->
|
||||
@objectsWeakMap.get id
|
||||
|
||||
exports.get = (id) ->
|
||||
objectsWeakMap.get id
|
||||
# Remove an object according to its storeId.
|
||||
remove: (processId, routingId, storeId) ->
|
||||
ObjectsStore.forRenderView(processId, routingId).remove storeId
|
||||
|
||||
exports.getAllWindows = () ->
|
||||
keys = windowsWeakMap.keys()
|
||||
windowsWeakMap.get key for key in keys
|
||||
# Clear all references to objects from renderer view.
|
||||
clear: (processId, routingId) ->
|
||||
@emit "release-renderer-view-#{processId}-#{routingId}"
|
||||
ObjectsStore.releaseForRenderView processId, routingId
|
||||
|
||||
exports.remove = (processId, routingId, storeId) ->
|
||||
ObjectsStore.forRenderView(processId, routingId).remove storeId
|
||||
# Return an array of all browser windows.
|
||||
getAllWindows: ->
|
||||
keys = @windowsWeakMap.keys()
|
||||
@windowsWeakMap.get key for key in keys
|
||||
|
||||
exports.clear = (processId, routingId) ->
|
||||
ObjectsStore.releaseForRenderView processId, routingId
|
||||
module.exports = new ObjectsRegistry
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -31,7 +31,6 @@ app.on('finish-launching', function() {
|
|||
});
|
||||
|
||||
mainWindow.on('closed', function() {
|
||||
console.log('closed');
|
||||
mainWindow = null;
|
||||
});
|
||||
|
||||
|
@ -39,129 +38,167 @@ app.on('finish-launching', function() {
|
|||
console.log('unresponsive');
|
||||
});
|
||||
|
||||
var template = [
|
||||
{
|
||||
label: 'Atom Shell',
|
||||
submenu: [
|
||||
{
|
||||
label: 'About Atom Shell',
|
||||
selector: 'orderFrontStandardAboutPanel:'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Hide Atom Shell',
|
||||
accelerator: 'Command+H',
|
||||
selector: 'hide:'
|
||||
},
|
||||
{
|
||||
label: 'Hide Others',
|
||||
accelerator: 'Command+Shift+H',
|
||||
selector: 'hideOtherApplications:'
|
||||
},
|
||||
{
|
||||
label: 'Show All',
|
||||
selector: 'unhideAllApplications:'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Quit',
|
||||
accelerator: 'Command+Q',
|
||||
click: function() { app.quit(); }
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Undo',
|
||||
accelerator: 'Command+Z',
|
||||
selector: 'undo:'
|
||||
},
|
||||
{
|
||||
label: 'Redo',
|
||||
accelerator: 'Shift+Command+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:'
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Reload',
|
||||
accelerator: 'Command+R',
|
||||
click: function() { BrowserWindow.getFocusedWindow().restart(); }
|
||||
},
|
||||
{
|
||||
label: 'Enter Fullscreen',
|
||||
click: function() { BrowserWindow.getFocusedWindow().setFullscreen(true); }
|
||||
},
|
||||
{
|
||||
label: 'Toggle DevTools',
|
||||
accelerator: 'Alt+Command+I',
|
||||
click: function() { BrowserWindow.getFocusedWindow().toggleDevTools(); }
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Window',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Minimize',
|
||||
accelerator: 'Command+M',
|
||||
selector: 'performMiniaturize:'
|
||||
},
|
||||
{
|
||||
label: 'Close',
|
||||
accelerator: 'Command+W',
|
||||
selector: 'performClose:'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Bring All to Front',
|
||||
selector: 'arrangeInFront:'
|
||||
},
|
||||
]
|
||||
},
|
||||
];
|
||||
if (process.platform == 'darwin') {
|
||||
var template = [
|
||||
{
|
||||
label: 'Atom Shell',
|
||||
submenu: [
|
||||
{
|
||||
label: 'About Atom Shell',
|
||||
selector: 'orderFrontStandardAboutPanel:'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Hide Atom Shell',
|
||||
accelerator: 'Command+H',
|
||||
selector: 'hide:'
|
||||
},
|
||||
{
|
||||
label: 'Hide Others',
|
||||
accelerator: 'Command+Shift+H',
|
||||
selector: 'hideOtherApplications:'
|
||||
},
|
||||
{
|
||||
label: 'Show All',
|
||||
selector: 'unhideAllApplications:'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Quit',
|
||||
accelerator: 'Command+Q',
|
||||
click: function() { app.quit(); }
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Edit',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Undo',
|
||||
accelerator: 'Command+Z',
|
||||
selector: 'undo:'
|
||||
},
|
||||
{
|
||||
label: 'Redo',
|
||||
accelerator: 'Shift+Command+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:'
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Reload',
|
||||
accelerator: 'Command+R',
|
||||
click: function() { BrowserWindow.getFocusedWindow().restart(); }
|
||||
},
|
||||
{
|
||||
label: 'Enter Fullscreen',
|
||||
click: function() { BrowserWindow.getFocusedWindow().setFullscreen(true); }
|
||||
},
|
||||
{
|
||||
label: 'Toggle DevTools',
|
||||
accelerator: 'Alt+Command+I',
|
||||
click: function() { BrowserWindow.getFocusedWindow().toggleDevTools(); }
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Window',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Minimize',
|
||||
accelerator: 'Command+M',
|
||||
selector: 'performMiniaturize:'
|
||||
},
|
||||
{
|
||||
label: 'Close',
|
||||
accelerator: 'Command+W',
|
||||
selector: 'performClose:'
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
},
|
||||
{
|
||||
label: 'Bring All to Front',
|
||||
selector: 'arrangeInFront:'
|
||||
},
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
menu = Menu.buildFromTemplate(template);
|
||||
|
||||
if (process.platform == 'darwin')
|
||||
menu = Menu.buildFromTemplate(template);
|
||||
Menu.setApplicationMenu(menu);
|
||||
} else {
|
||||
var template = [
|
||||
{
|
||||
label: 'File',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Open',
|
||||
accelerator: 'Command+O',
|
||||
},
|
||||
{
|
||||
label: 'Close',
|
||||
accelerator: 'Command+W',
|
||||
click: function() { mainWindow.close(); }
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Reload',
|
||||
accelerator: 'Command+R',
|
||||
click: function() { mainWindow.restart(); }
|
||||
},
|
||||
{
|
||||
label: 'Enter Fullscreen',
|
||||
click: function() { mainWindow.setFullscreen(true); }
|
||||
},
|
||||
{
|
||||
label: 'Toggle DevTools',
|
||||
accelerator: 'Alt+Command+I',
|
||||
click: function() { mainWindow.toggleDevTools(); }
|
||||
},
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
menu = Menu.buildFromTemplate(template);
|
||||
mainWindow.setMenu(menu);
|
||||
}
|
||||
|
||||
ipc.on('message', function(processId, routingId, type) {
|
||||
console.log(type);
|
||||
if (type == 'menu')
|
||||
menu.popup(mainWindow);
|
||||
});
|
||||
|
|
|
@ -4,8 +4,12 @@
|
|||
|
||||
#include "browser/native_window_win.h"
|
||||
|
||||
#include "base/stl_util.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/values.h"
|
||||
#include "browser/api/atom_api_menu.h"
|
||||
#include "browser/ui/win/menu_2.h"
|
||||
#include "browser/ui/win/native_menu_win.h"
|
||||
#include "common/draggable_region.h"
|
||||
#include "common/options_switches.h"
|
||||
#include "content/public/browser/native_web_keyboard_event.h"
|
||||
|
@ -14,8 +18,10 @@
|
|||
#include "content/public/browser/web_contents.h"
|
||||
#include "content/public/browser/web_contents_view.h"
|
||||
#include "ui/gfx/path.h"
|
||||
#include "ui/base/models/simple_menu_model.h"
|
||||
#include "ui/views/controls/webview/webview.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
#include "ui/views/widget/native_widget_win.h"
|
||||
#include "ui/views/window/client_view.h"
|
||||
#include "ui/views/window/native_frame_view.h"
|
||||
|
||||
|
@ -26,24 +32,49 @@ namespace {
|
|||
const int kResizeInsideBoundsSize = 5;
|
||||
const int kResizeAreaCornerSize = 16;
|
||||
|
||||
// Wrapper of NativeWidgetWin to handle WM_MENUCOMMAND messages, which are
|
||||
// triggered by window menus.
|
||||
class MenuCommandNativeWidget : public views::NativeWidgetWin {
|
||||
public:
|
||||
explicit MenuCommandNativeWidget(NativeWindowWin* delegate)
|
||||
: views::NativeWidgetWin(delegate->window()),
|
||||
delegate_(delegate) {}
|
||||
virtual ~MenuCommandNativeWidget() {}
|
||||
|
||||
protected:
|
||||
virtual bool PreHandleMSG(UINT message,
|
||||
WPARAM w_param,
|
||||
LPARAM l_param,
|
||||
LRESULT* result) OVERRIDE {
|
||||
if (message == WM_MENUCOMMAND) {
|
||||
delegate_->OnMenuCommand(w_param, reinterpret_cast<HMENU>(l_param));
|
||||
*result = 0;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
NativeWindowWin* delegate_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MenuCommandNativeWidget);
|
||||
};
|
||||
|
||||
class NativeWindowClientView : public views::ClientView {
|
||||
public:
|
||||
NativeWindowClientView(views::Widget* widget,
|
||||
views::View* contents_view,
|
||||
NativeWindowWin* shell)
|
||||
: views::ClientView(widget, contents_view),
|
||||
shell_(shell) {
|
||||
NativeWindowWin* contents_view)
|
||||
: views::ClientView(widget, contents_view) {
|
||||
}
|
||||
virtual ~NativeWindowClientView() {}
|
||||
|
||||
virtual bool CanClose() OVERRIDE {
|
||||
shell_->CloseWebContents();
|
||||
static_cast<NativeWindowWin*>(contents_view())->CloseWebContents();
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
NativeWindowWin* shell_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(NativeWindowClientView);
|
||||
};
|
||||
|
||||
|
@ -173,6 +204,7 @@ NativeWindowWin::NativeWindowWin(content::WebContents* web_contents,
|
|||
resizable_(true) {
|
||||
views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
|
||||
params.delegate = this;
|
||||
params.native_widget = new MenuCommandNativeWidget(this);
|
||||
params.remove_standard_frame = !has_frame_;
|
||||
params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
|
||||
window_->set_frame_type(views::Widget::FRAME_TYPE_FORCE_NATIVE);
|
||||
|
@ -329,6 +361,17 @@ gfx::NativeWindow NativeWindowWin::GetNativeWindow() {
|
|||
return window_->GetNativeView();
|
||||
}
|
||||
|
||||
void NativeWindowWin::OnMenuCommand(int position, HMENU menu) {
|
||||
DCHECK(menu_);
|
||||
menu_->wrapper()->OnMenuCommand(position, menu);
|
||||
}
|
||||
|
||||
void NativeWindowWin::SetMenu(ui::MenuModel* menu_model) {
|
||||
menu_.reset(new atom::Menu2(menu_model, true));
|
||||
::SetMenu(GetNativeWindow(), menu_->GetNativeMenu());
|
||||
RegisterAccelerators();
|
||||
}
|
||||
|
||||
void NativeWindowWin::UpdateDraggableRegions(
|
||||
const std::vector<DraggableRegion>& regions) {
|
||||
if (has_frame_)
|
||||
|
@ -356,12 +399,54 @@ void NativeWindowWin::UpdateDraggableRegions(
|
|||
void NativeWindowWin::HandleKeyboardEvent(
|
||||
content::WebContents*,
|
||||
const content::NativeWebKeyboardEvent& event) {
|
||||
if (event.type == WebKit::WebInputEvent::KeyUp) {
|
||||
ui::Accelerator accelerator(
|
||||
static_cast<ui::KeyboardCode>(event.windowsKeyCode),
|
||||
content::GetModifiersFromNativeWebKeyboardEvent(event));
|
||||
|
||||
if (GetFocusManager()->ProcessAccelerator(accelerator)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Any unhandled keyboard/character messages should be defproced.
|
||||
// This allows stuff like F10, etc to work correctly.
|
||||
DefWindowProc(event.os_event.hwnd, event.os_event.message,
|
||||
event.os_event.wParam, event.os_event.lParam);
|
||||
}
|
||||
|
||||
void NativeWindowWin::Layout() {
|
||||
DCHECK(web_view_);
|
||||
web_view_->SetBounds(0, 0, width(), height());
|
||||
OnViewWasResized();
|
||||
}
|
||||
|
||||
void NativeWindowWin::ViewHierarchyChanged(bool is_add,
|
||||
views::View* parent,
|
||||
views::View* child) {
|
||||
if (is_add && child == this)
|
||||
AddChildView(web_view_);
|
||||
}
|
||||
|
||||
bool NativeWindowWin::AcceleratorPressed(
|
||||
const ui::Accelerator& accelerator) {
|
||||
if (ContainsKey(accelerator_table_, accelerator)) {
|
||||
const MenuItem& item = accelerator_table_[accelerator];
|
||||
item.model->ActivatedAt(item.position);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void NativeWindowWin::DeleteDelegate() {
|
||||
// Do nothing, window is managed by users.
|
||||
}
|
||||
|
||||
views::View* NativeWindowWin::GetInitiallyFocusedView() {
|
||||
return web_view_;
|
||||
}
|
||||
|
||||
bool NativeWindowWin::CanResize() const {
|
||||
return resizable_;
|
||||
}
|
||||
|
@ -391,7 +476,7 @@ const views::Widget* NativeWindowWin::GetWidget() const {
|
|||
}
|
||||
|
||||
views::ClientView* NativeWindowWin::CreateClientView(views::Widget* widget) {
|
||||
return new NativeWindowClientView(widget, web_view_, this);
|
||||
return new NativeWindowClientView(widget, this);
|
||||
}
|
||||
|
||||
views::NonClientFrameView* NativeWindowWin::CreateNonClientFrameView(
|
||||
|
@ -432,6 +517,44 @@ void NativeWindowWin::OnViewWasResized() {
|
|||
web_contents->GetRenderViewHost()->GetView()->SetClickthroughRegion(rgn);
|
||||
}
|
||||
|
||||
void NativeWindowWin::RegisterAccelerators() {
|
||||
views::FocusManager* focus_manager = GetFocusManager();
|
||||
accelerator_table_.clear();
|
||||
focus_manager->UnregisterAccelerators(this);
|
||||
|
||||
GenerateAcceleratorTable();
|
||||
for (AcceleratorTable::const_iterator iter = accelerator_table_.begin();
|
||||
iter != accelerator_table_.end(); ++iter) {
|
||||
focus_manager->RegisterAccelerator(
|
||||
iter->first, ui::AcceleratorManager::kNormalPriority, this);
|
||||
}
|
||||
}
|
||||
|
||||
void NativeWindowWin::GenerateAcceleratorTable() {
|
||||
DCHECK(menu_);
|
||||
ui::SimpleMenuModel* model = static_cast<ui::SimpleMenuModel*>(
|
||||
menu_->model());
|
||||
FillAcceleratorTable(&accelerator_table_, model);
|
||||
}
|
||||
|
||||
void NativeWindowWin::FillAcceleratorTable(AcceleratorTable* table,
|
||||
ui::MenuModel* model) {
|
||||
int count = model->GetItemCount();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
ui::MenuModel::ItemType type = model->GetTypeAt(i);
|
||||
if (type == ui::MenuModel::TYPE_SUBMENU) {
|
||||
ui::MenuModel* submodel = model->GetSubmenuModelAt(i);
|
||||
FillAcceleratorTable(table, submodel);
|
||||
} else {
|
||||
ui::Accelerator accelerator;
|
||||
if (model->GetAcceleratorAt(i, &accelerator)) {
|
||||
MenuItem item = { i, model };
|
||||
(*table)[accelerator] = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
NativeWindow* NativeWindow::Create(content::WebContents* web_contents,
|
||||
base::DictionaryValue* options) {
|
||||
|
|
|
@ -12,6 +12,10 @@
|
|||
#include "ui/gfx/size.h"
|
||||
#include "ui/views/widget/widget_delegate.h"
|
||||
|
||||
namespace ui {
|
||||
class MenuModel;
|
||||
}
|
||||
|
||||
namespace views {
|
||||
class WebView;
|
||||
class Widget;
|
||||
|
@ -19,8 +23,10 @@ class Widget;
|
|||
|
||||
namespace atom {
|
||||
|
||||
class Menu2;
|
||||
|
||||
class NativeWindowWin : public NativeWindow,
|
||||
public views::WidgetDelegate {
|
||||
public views::WidgetDelegateView {
|
||||
public:
|
||||
explicit NativeWindowWin(content::WebContents* web_contents,
|
||||
base::DictionaryValue* options);
|
||||
|
@ -61,6 +67,12 @@ class NativeWindowWin : public NativeWindow,
|
|||
virtual bool IsKiosk() OVERRIDE;
|
||||
virtual gfx::NativeWindow GetNativeWindow() OVERRIDE;
|
||||
|
||||
void OnMenuCommand(int position, HMENU menu);
|
||||
|
||||
// Set the native window menu.
|
||||
void SetMenu(ui::MenuModel* menu_model);
|
||||
|
||||
views::Widget* window() const { return window_.get(); }
|
||||
SkRegion* draggable_region() { return draggable_region_.get(); }
|
||||
|
||||
protected:
|
||||
|
@ -72,7 +84,16 @@ class NativeWindowWin : public NativeWindow,
|
|||
content::WebContents*,
|
||||
const content::NativeWebKeyboardEvent&) OVERRIDE;
|
||||
|
||||
// Overridden from views::View:
|
||||
virtual void Layout() OVERRIDE;
|
||||
virtual void ViewHierarchyChanged(bool is_add,
|
||||
views::View* parent,
|
||||
views::View* child) OVERRIDE;
|
||||
virtual bool AcceleratorPressed(const ui::Accelerator& accelerator) OVERRIDE;
|
||||
|
||||
// Overridden from views::WidgetDelegate:
|
||||
virtual void DeleteDelegate() OVERRIDE;
|
||||
virtual views::View* GetInitiallyFocusedView() OVERRIDE;
|
||||
virtual bool CanResize() const OVERRIDE;
|
||||
virtual bool CanMaximize() const OVERRIDE;
|
||||
virtual string16 GetWindowTitle() const OVERRIDE;
|
||||
|
@ -85,11 +106,30 @@ class NativeWindowWin : public NativeWindow,
|
|||
views::Widget* widget) OVERRIDE;
|
||||
|
||||
private:
|
||||
typedef struct { int position; ui::MenuModel* model; } MenuItem;
|
||||
typedef std::map<ui::Accelerator, MenuItem> AcceleratorTable;
|
||||
|
||||
void OnViewWasResized();
|
||||
|
||||
// Register accelerators supported by the menu model.
|
||||
void RegisterAccelerators();
|
||||
|
||||
// Generate a table that contains memu model's accelerators and command ids.
|
||||
void GenerateAcceleratorTable();
|
||||
|
||||
// Helper to fill the accelerator table from the model.
|
||||
void FillAcceleratorTable(AcceleratorTable* table,
|
||||
ui::MenuModel* model);
|
||||
|
||||
scoped_ptr<views::Widget> window_;
|
||||
views::WebView* web_view_; // managed by window_.
|
||||
|
||||
// The window menu.
|
||||
scoped_ptr<atom::Menu2> menu_;
|
||||
|
||||
// Map from accelerator to menu item's command id.
|
||||
AcceleratorTable accelerator_table_;
|
||||
|
||||
scoped_ptr<SkRegion> draggable_region_;
|
||||
|
||||
bool resizable_;
|
||||
|
|
|
@ -15,10 +15,9 @@ namespace accelerator_util {
|
|||
|
||||
namespace {
|
||||
|
||||
// For Mac, we convert "Ctrl" to "Command" and "MacCtrl" to "Ctrl". Other
|
||||
// platforms leave the shortcut untouched.
|
||||
// Convert "Command" to "Ctrl" on non-Mac
|
||||
std::string NormalizeShortcutSuggestion(const std::string& suggestion) {
|
||||
#if !defined(OS_MACOSX)
|
||||
#if defined(OS_MACOSX)
|
||||
return suggestion;
|
||||
#endif
|
||||
|
||||
|
@ -26,9 +25,7 @@ std::string NormalizeShortcutSuggestion(const std::string& suggestion) {
|
|||
std::vector<std::string> tokens;
|
||||
base::SplitString(suggestion, '+', &tokens);
|
||||
for (size_t i = 0; i < tokens.size(); i++) {
|
||||
if (tokens[i] == "Ctrl")
|
||||
tokens[i] = "Command";
|
||||
else if (tokens[i] == "MacCtrl")
|
||||
if (tokens[i] == "Command")
|
||||
tokens[i] = "Ctrl";
|
||||
}
|
||||
return JoinString(tokens, '+');
|
||||
|
|
|
@ -77,6 +77,7 @@ class Menu2 {
|
|||
|
||||
// Accessors.
|
||||
ui::MenuModel* model() const { return model_; }
|
||||
NativeMenuWin* wrapper() const { return wrapper_.get(); }
|
||||
|
||||
// Sets the minimum width of the menu.
|
||||
void SetMinimumWidth(int width);
|
||||
|
|
|
@ -92,6 +92,20 @@ class NativeMenuWin::MenuHostWindow {
|
|||
DestroyWindow(hwnd_);
|
||||
}
|
||||
|
||||
// Called when the user selects a specific item.
|
||||
void OnMenuCommand(int position, HMENU menu) {
|
||||
NativeMenuWin* menu_win = GetNativeMenuWinFromHMENU(menu);
|
||||
ui::MenuModel* model = menu_win->model_;
|
||||
NativeMenuWin* root_menu = menu_win;
|
||||
while (root_menu->parent_)
|
||||
root_menu = root_menu->parent_;
|
||||
|
||||
// Only notify the model if it didn't already send out notification.
|
||||
// See comment in MenuMessageHook for details.
|
||||
if (root_menu->menu_action_ == MENU_ACTION_NONE)
|
||||
model->ActivatedAt(position);
|
||||
}
|
||||
|
||||
HWND hwnd() const { return hwnd_; }
|
||||
|
||||
private:
|
||||
|
@ -146,20 +160,6 @@ class NativeMenuWin::MenuHostWindow {
|
|||
return reinterpret_cast<NativeMenuWin::ItemData*>(item_data);
|
||||
}
|
||||
|
||||
// Called when the user selects a specific item.
|
||||
void OnMenuCommand(int position, HMENU menu) {
|
||||
NativeMenuWin* menu_win = GetNativeMenuWinFromHMENU(menu);
|
||||
ui::MenuModel* model = menu_win->model_;
|
||||
NativeMenuWin* root_menu = menu_win;
|
||||
while (root_menu->parent_)
|
||||
root_menu = root_menu->parent_;
|
||||
|
||||
// Only notify the model if it didn't already send out notification.
|
||||
// See comment in MenuMessageHook for details.
|
||||
if (root_menu->menu_action_ == MENU_ACTION_NONE)
|
||||
model->ActivatedAt(position);
|
||||
}
|
||||
|
||||
// Called as the user moves their mouse or arrows through the contents of the
|
||||
// menu.
|
||||
void OnMenuSelect(WPARAM w_param, HMENU menu) {
|
||||
|
@ -529,6 +529,10 @@ void NativeMenuWin::SetMinimumWidth(int width) {
|
|||
NOTIMPLEMENTED();
|
||||
}
|
||||
|
||||
void NativeMenuWin::OnMenuCommand(int position, HMENU menu) {
|
||||
host_window_->OnMenuCommand(position, menu);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// NativeMenuWin, private:
|
||||
|
||||
|
|
|
@ -56,6 +56,9 @@ class NativeMenuWin {
|
|||
void RemoveMenuListener(views::MenuListener* listener);
|
||||
void SetMinimumWidth(int width);
|
||||
|
||||
// Called by user to generate a menu command event.
|
||||
void OnMenuCommand(int position, HMENU menu);
|
||||
|
||||
// Flag to create a window menu instead of popup menu.
|
||||
void set_create_as_window_menu(bool flag) { create_as_window_menu_ = flag; }
|
||||
bool create_as_window_menu() const { return create_as_window_menu_; }
|
||||
|
|
|
@ -79,12 +79,16 @@ code will not run.
|
|||
|
||||
Returns the version of current bundle or executable.
|
||||
|
||||
## app.getBrowserWindows()
|
||||
|
||||
Returns an array of all browser windows.
|
||||
|
||||
## app.commandLine.appendSwitch(switch, [value])
|
||||
|
||||
Append a switch [with optional value] to Chromium's command line.
|
||||
|
||||
**Note:** This will not affect `process.argv`, and is mainly used by
|
||||
**developers to control some low-level Chromium behaviors.
|
||||
**Note:** This will not affect `process.argv`, and is mainly used by developers
|
||||
to control some low-level Chromium behaviors.
|
||||
|
||||
## app.commandLine.appendArgument(value)
|
||||
|
||||
|
|
|
@ -23,5 +23,5 @@
|
|||
|
||||
## Notes on accelerator
|
||||
|
||||
On OS X, the `Ctrl` would automatically translated to `Command`, if you really
|
||||
want `Ctrl` on OS X, you should use `MacCtrl`.
|
||||
On Linux and Windows, the `Command` would be translated to `Ctrl`, so usually
|
||||
you can use `Command` for most of the commands.
|
||||
|
|
40
spec/main.js
40
spec/main.js
|
@ -1,6 +1,7 @@
|
|||
var app = require('app');
|
||||
var ipc = require('ipc');
|
||||
var BrowserWindow = require('browser-window');
|
||||
var Menu = require('menu');
|
||||
|
||||
var window = null;
|
||||
|
||||
|
@ -39,6 +40,45 @@ app.on('window-all-closed', function() {
|
|||
});
|
||||
|
||||
app.on('finish-launching', function() {
|
||||
var template = [
|
||||
{
|
||||
label: 'File',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Open',
|
||||
accelerator: 'Command+O',
|
||||
},
|
||||
{
|
||||
label: 'Close',
|
||||
accelerator: 'Command+W',
|
||||
click: function(item, window) { window.close(); }
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'View',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Reload',
|
||||
accelerator: 'Command+R',
|
||||
click: function(item, window) { window.restart(); }
|
||||
},
|
||||
{
|
||||
label: 'Enter Fullscreen',
|
||||
click: function(item, window) { window.setFullScreen(true); }
|
||||
},
|
||||
{
|
||||
label: 'Toggle DevTools',
|
||||
accelerator: 'Alt+Command+I',
|
||||
click: function(item, window) { window.toggleDevTools(); }
|
||||
},
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
var menu = Menu.buildFromTemplate(template);
|
||||
app.setApplicationMenu(menu);
|
||||
|
||||
// Test if using protocol module would crash.
|
||||
require('protocol').registerProtocol('test-if-crashes', function() {});
|
||||
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
describe 'http', ->
|
||||
describe 'sending request of http protocol urls', ->
|
||||
it 'should not crash', (done) ->
|
||||
$.ajax
|
||||
url: 'http://127.0.0.1'
|
||||
success: -> done()
|
||||
error: -> done()
|
||||
it 'should not crash', ->
|
||||
$.get 'https://api.github.com/zen'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue