Merge pull request #105 from atom/windows-menu

Implement menu API on Windows, fixes #75.
This commit is contained in:
Cheng Zhao 2013-10-06 17:58:42 -07:00
commit 6a712d4db4
21 changed files with 530 additions and 200 deletions

View file

@ -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)

View file

@ -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);

View file

@ -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

View file

@ -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();
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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}")

View file

@ -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);
});

View file

@ -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) {

View file

@ -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_;

View file

@ -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, '+');

View file

@ -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);

View file

@ -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:

View file

@ -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_; }

View file

@ -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)

View file

@ -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.

View file

@ -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() {});

View file

@ -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'