electron/browser/native_window_win.cc
Cheng Zhao 6a712d4db4 Merge pull request #105 from atom/windows-menu
Implement menu API on Windows, fixes #75.
2013-10-06 17:58:42 -07:00

564 lines
16 KiB
C++

// Copyright (c) 2013 GitHub, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#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"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#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"
namespace atom {
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,
NativeWindowWin* contents_view)
: views::ClientView(widget, contents_view) {
}
virtual ~NativeWindowClientView() {}
virtual bool CanClose() OVERRIDE {
static_cast<NativeWindowWin*>(contents_view())->CloseWebContents();
return false;
}
private:
DISALLOW_COPY_AND_ASSIGN(NativeWindowClientView);
};
class NativeWindowFrameView : public views::NativeFrameView {
public:
explicit NativeWindowFrameView(views::Widget* frame, NativeWindowWin* shell)
: NativeFrameView(frame),
shell_(shell) {
}
virtual ~NativeWindowFrameView() {}
virtual gfx::Size GetMinimumSize() OVERRIDE {
return shell_->GetMinimumSize();
}
virtual gfx::Size GetMaximumSize() OVERRIDE {
return shell_->GetMaximumSize();
}
private:
NativeWindowWin* shell_;
DISALLOW_COPY_AND_ASSIGN(NativeWindowFrameView);
};
class NativeWindowFramelessView : public views::NonClientFrameView {
public:
explicit NativeWindowFramelessView(views::Widget* frame,
NativeWindowWin* shell)
: frame_(frame),
shell_(shell) {
}
virtual ~NativeWindowFramelessView() {}
// views::NonClientFrameView implementations:
virtual gfx::Rect NativeWindowFramelessView::GetBoundsForClientView() const
OVERRIDE {
return bounds();
}
virtual gfx::Rect NativeWindowFramelessView::GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const OVERRIDE {
gfx::Rect window_bounds = client_bounds;
// Enforce minimum size (1, 1) in case that client_bounds is passed with
// empty size. This could occur when the frameless window is being
// initialized.
if (window_bounds.IsEmpty()) {
window_bounds.set_width(1);
window_bounds.set_height(1);
}
return window_bounds;
}
virtual int NonClientHitTest(const gfx::Point& point) OVERRIDE {
if (frame_->IsFullscreen())
return HTCLIENT;
// Check the frame first, as we allow a small area overlapping the contents
// to be used for resize handles.
bool can_ever_resize = frame_->widget_delegate() ?
frame_->widget_delegate()->CanResize() :
false;
// Don't allow overlapping resize handles when the window is maximized or
// fullscreen, as it can't be resized in those states.
int resize_border =
frame_->IsMaximized() || frame_->IsFullscreen() ? 0 :
kResizeInsideBoundsSize;
int frame_component = GetHTComponentForFrame(point,
resize_border,
resize_border,
kResizeAreaCornerSize,
kResizeAreaCornerSize,
can_ever_resize);
if (frame_component != HTNOWHERE)
return frame_component;
// Check for possible draggable region in the client area for the frameless
// window.
if (shell_->draggable_region() &&
shell_->draggable_region()->contains(point.x(), point.y()))
return HTCAPTION;
int client_component = frame_->client_view()->NonClientHitTest(point);
if (client_component != HTNOWHERE)
return client_component;
// Caption is a safe default.
return HTCAPTION;
}
virtual void GetWindowMask(const gfx::Size& size,
gfx::Path* window_mask) OVERRIDE {}
virtual void ResetWindowControls() OVERRIDE {}
virtual void UpdateWindowIcon() OVERRIDE {}
virtual void UpdateWindowTitle() OVERRIDE {}
// views::View implementations:
virtual gfx::Size NativeWindowFramelessView::GetPreferredSize() OVERRIDE {
gfx::Size pref = frame_->client_view()->GetPreferredSize();
gfx::Rect bounds(0, 0, pref.width(), pref.height());
return frame_->non_client_view()->GetWindowBoundsForClientBounds(
bounds).size();
}
virtual gfx::Size GetMinimumSize() OVERRIDE {
return shell_->GetMinimumSize();
}
virtual gfx::Size GetMaximumSize() OVERRIDE {
return shell_->GetMaximumSize();
}
private:
views::Widget* frame_;
NativeWindowWin* shell_;
DISALLOW_COPY_AND_ASSIGN(NativeWindowFramelessView);
};
} // namespace
NativeWindowWin::NativeWindowWin(content::WebContents* web_contents,
base::DictionaryValue* options)
: NativeWindow(web_contents, options),
window_(new views::Widget),
web_view_(new views::WebView(NULL)),
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);
window_->Init(params);
int width = 800, height = 600;
options->GetInteger(switches::kWidth, &width);
options->GetInteger(switches::kHeight, &height);
gfx::Size size(width, height);
window_->CenterWindow(size);
web_view_->SetWebContents(web_contents);
OnViewWasResized();
}
NativeWindowWin::~NativeWindowWin() {
}
void NativeWindowWin::Close() {
window_->Close();
}
void NativeWindowWin::CloseImmediately() {
window_->CloseNow();
}
void NativeWindowWin::Move(const gfx::Rect& bounds) {
window_->SetBounds(bounds);
}
void NativeWindowWin::Focus(bool focus) {
if (focus)
window_->Activate();
else
window_->Deactivate();
}
bool NativeWindowWin::IsFocused() {
return window_->IsActive();
}
void NativeWindowWin::Show() {
window_->Show();
}
void NativeWindowWin::Hide() {
window_->Hide();
}
void NativeWindowWin::Maximize() {
window_->Maximize();
}
void NativeWindowWin::Unmaximize() {
window_->Restore();
}
bool NativeWindowWin::IsVisible() {
return window_->IsVisible();
}
void NativeWindowWin::Minimize() {
window_->Minimize();
}
void NativeWindowWin::Restore() {
window_->Restore();
}
void NativeWindowWin::SetFullscreen(bool fullscreen) {
window_->SetFullscreen(fullscreen);
}
bool NativeWindowWin::IsFullscreen() {
return window_->IsFullscreen();
}
void NativeWindowWin::SetSize(const gfx::Size& size) {
window_->SetSize(size);
}
gfx::Size NativeWindowWin::GetSize() {
return window_->GetWindowBoundsInScreen().size();
}
void NativeWindowWin::SetMinimumSize(const gfx::Size& size) {
minimum_size_ = size;
}
gfx::Size NativeWindowWin::GetMinimumSize() {
return minimum_size_;
}
void NativeWindowWin::SetMaximumSize(const gfx::Size& size) {
maximum_size_ = size;
}
gfx::Size NativeWindowWin::GetMaximumSize() {
return maximum_size_;
}
void NativeWindowWin::SetResizable(bool resizable) {
resizable_ = resizable;
}
bool NativeWindowWin::IsResizable() {
return resizable_;
}
void NativeWindowWin::SetAlwaysOnTop(bool top) {
window_->SetAlwaysOnTop(top);
}
bool NativeWindowWin::IsAlwaysOnTop() {
DWORD style = ::GetWindowLong(window_->GetNativeView(), GWL_EXSTYLE);
return style & WS_EX_TOPMOST;
}
void NativeWindowWin::Center() {
window_->CenterWindow(GetSize());
}
void NativeWindowWin::SetPosition(const gfx::Point& position) {
window_->SetBounds(gfx::Rect(position, GetSize()));
}
gfx::Point NativeWindowWin::GetPosition() {
return window_->GetWindowBoundsInScreen().origin();
}
void NativeWindowWin::SetTitle(const std::string& title) {
title_ = UTF8ToUTF16(title);
window_->UpdateWindowTitle();
}
std::string NativeWindowWin::GetTitle() {
return UTF16ToUTF8(title_);
}
void NativeWindowWin::FlashFrame(bool flash) {
window_->FlashFrame(flash);
}
void NativeWindowWin::SetKiosk(bool kiosk) {
SetFullscreen(kiosk);
}
bool NativeWindowWin::IsKiosk() {
return IsFullscreen();
}
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_)
return;
SkRegion* draggable_region = new SkRegion;
// By default, the whole window is non-draggable. We need to explicitly
// include those draggable regions.
for (std::vector<DraggableRegion>::const_iterator iter = regions.begin();
iter != regions.end(); ++iter) {
const DraggableRegion& region = *iter;
draggable_region->op(
region.bounds.x(),
region.bounds.y(),
region.bounds.right(),
region.bounds.bottom(),
region.draggable ? SkRegion::kUnion_Op : SkRegion::kDifference_Op);
}
draggable_region_.reset(draggable_region);
OnViewWasResized();
}
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_;
}
bool NativeWindowWin::CanMaximize() const {
return resizable_;
}
string16 NativeWindowWin::GetWindowTitle() const {
return title_;
}
bool NativeWindowWin::ShouldHandleSystemCommands() const {
return true;
}
bool NativeWindowWin::ShouldShowWindowIcon() const {
return true;
}
views::Widget* NativeWindowWin::GetWidget() {
return window_.get();
}
const views::Widget* NativeWindowWin::GetWidget() const {
return window_.get();
}
views::ClientView* NativeWindowWin::CreateClientView(views::Widget* widget) {
return new NativeWindowClientView(widget, this);
}
views::NonClientFrameView* NativeWindowWin::CreateNonClientFrameView(
views::Widget* widget) {
if (has_frame_)
return new NativeWindowFrameView(widget, this);
return new NativeWindowFramelessView(widget, this);
}
void NativeWindowWin::OnViewWasResized() {
// Set the window shape of the RWHV.
gfx::Size sz = web_view_->size();
int height = sz.height(), width = sz.width();
gfx::Path path;
path.addRect(0, 0, width, height);
SetWindowRgn(web_contents()->GetView()->GetNativeView(),
path.CreateNativeRegion(),
1);
SkRegion* rgn = new SkRegion;
if (!window_->IsFullscreen() && !window_->IsMaximized()) {
if (draggable_region())
rgn->op(*draggable_region(), SkRegion::kUnion_Op);
if (!has_frame_ && CanResize()) {
rgn->op(0, 0, width, kResizeInsideBoundsSize, SkRegion::kUnion_Op);
rgn->op(0, 0, kResizeInsideBoundsSize, height, SkRegion::kUnion_Op);
rgn->op(width - kResizeInsideBoundsSize, 0, width, height,
SkRegion::kUnion_Op);
rgn->op(0, height - kResizeInsideBoundsSize, width, height,
SkRegion::kUnion_Op);
}
}
content::WebContents* web_contents = GetWebContents();
if (web_contents->GetRenderViewHost()->GetView())
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) {
return new NativeWindowWin(web_contents, options);
}
} // namespace atom