diff --git a/atom.gyp b/atom.gyp index 249c9611215d..90f7286af250 100644 --- a/atom.gyp +++ b/atom.gyp @@ -143,6 +143,8 @@ 'atom/browser/ui/views/linux_frame_view.h', 'atom/browser/ui/views/menu_bar.cc', 'atom/browser/ui/views/menu_bar.h', + 'atom/browser/ui/views/menu_delegate.cc', + 'atom/browser/ui/views/menu_delegate.h', 'atom/browser/ui/views/menu_layout.cc', 'atom/browser/ui/views/menu_layout.h', 'atom/browser/ui/views/win_frame_view.cc', diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 86311e5d14e4..b123c09fd1bd 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -304,6 +304,9 @@ void NativeWindowViews::SetMenu(ui::MenuModel* menu_model) { AddChildViewAt(menu_bar_, 0); SetContentSize(content_size); } + + menu_bar_->SetMenu(menu_model); + Layout(); } gfx::NativeWindow NativeWindowViews::GetNativeWindow() { diff --git a/atom/browser/ui/views/menu_bar.cc b/atom/browser/ui/views/menu_bar.cc index 3580cd241ada..ef9277181de3 100644 --- a/atom/browser/ui/views/menu_bar.cc +++ b/atom/browser/ui/views/menu_bar.cc @@ -4,7 +4,11 @@ #include "atom/browser/ui/views/menu_bar.h" -#include "ui/gfx/canvas.h" +#include "atom/browser/ui/views/menu_delegate.h" +#include "ui/base/models/menu_model.h" +#include "ui/views/background.h" +#include "ui/views/controls/button/menu_button.h" +#include "ui/views/layout/box_layout.h" namespace atom { @@ -17,18 +21,75 @@ const SkColor kDefaultColor = SkColorSetARGB(255, 233, 233, 233); } // namespace -MenuBar::MenuBar() { +MenuBar::MenuBar() + : menu_model_(NULL) { + set_background(views::Background::CreateSolidBackground(kDefaultColor)); + SetLayoutManager(new views::BoxLayout( + views::BoxLayout::kHorizontal, 0, 0, 0)); } MenuBar::~MenuBar() { } -void MenuBar::Paint(gfx::Canvas* canvas) { - canvas->FillRect(bounds(), kDefaultColor); +void MenuBar::SetMenu(ui::MenuModel* model) { + menu_model_ = model; + RemoveAllChildViews(true); + + for (int i = 0; i < model->GetItemCount(); ++i) { + views::MenuButton* button = + new views::MenuButton(this, model->GetLabelAt(i), this, false); + button->set_tag(i); + AddChildView(button); + } +} + +int MenuBar::GetItemCount() const { + return menu_model_->GetItemCount(); +} + +bool MenuBar::GetMenuButtonFromScreenPoint(const gfx::Point& point, + ui::MenuModel** menu_model, + views::MenuButton** button) { + gfx::Point location(point); + views::View::ConvertPointFromScreen(this, &location); + + if (location.x() < 0 || location.x() >= width() || location.y() < 0 || + location.y() >= height()) + return false; + + for (int i = 0; i < child_count(); ++i) { + views::View* view = child_at(i); + if (view->bounds().Contains(location) && + (menu_model_->GetTypeAt(i) == ui::MenuModel::TYPE_SUBMENU)) { + *menu_model = menu_model_->GetSubmenuModelAt(i); + *button = static_cast(view); + return true; + } + } + + return false; } const char* MenuBar::GetClassName() const { return kViewClassName; } +void MenuBar::ButtonPressed(views::Button* sender, const ui::Event& event) { +} + +void MenuBar::OnMenuButtonClicked(views::View* source, + const gfx::Point& point) { + if (!menu_model_) + return; + + views::MenuButton* button = static_cast(source); + int id = button->tag(); + ui::MenuModel::ItemType type = menu_model_->GetTypeAt(id); + if (type != ui::MenuModel::TYPE_SUBMENU) + return; + + menu_delegate_.reset(new MenuDelegate(this)); + menu_delegate_->RunMenu(menu_model_->GetSubmenuModelAt(id), button); +} + } // namespace atom diff --git a/atom/browser/ui/views/menu_bar.h b/atom/browser/ui/views/menu_bar.h index 1b537f44b3bf..dbc3520e2abd 100644 --- a/atom/browser/ui/views/menu_bar.h +++ b/atom/browser/ui/views/menu_bar.h @@ -5,21 +5,56 @@ #ifndef ATOM_BROWSER_UI_VIEWS_MENU_BAR_H_ #define ATOM_BROWSER_UI_VIEWS_MENU_BAR_H_ +#include "ui/views/controls/button/button.h" +#include "ui/views/controls/button/menu_button_listener.h" #include "ui/views/view.h" +namespace ui { +class MenuModel; +} + +namespace views { +class MenuButton; +} + namespace atom { -class MenuBar : public views::View { +class MenuDelegate; + +class MenuBar : public views::View, + public views::ButtonListener, + public views::MenuButtonListener { public: MenuBar(); virtual ~MenuBar(); + // Replaces current menu with a new one. + void SetMenu(ui::MenuModel* menu_model); + + // Returns there are how many items in the root menu. + int GetItemCount() const; + + // Get the menu under specified screen point. + bool GetMenuButtonFromScreenPoint(const gfx::Point& point, + ui::MenuModel** menu_model, + views::MenuButton** button); + protected: // views::View: - virtual void Paint(gfx::Canvas* canvas) OVERRIDE; virtual const char* GetClassName() const OVERRIDE; + // views::ButtonListener: + virtual void ButtonPressed(views::Button* sender, + const ui::Event& event) OVERRIDE; + + // views::MenuButtonListener: + virtual void OnMenuButtonClicked(views::View* source, + const gfx::Point& point) OVERRIDE; + private: + ui::MenuModel* menu_model_; + scoped_ptr menu_delegate_; + DISALLOW_COPY_AND_ASSIGN(MenuBar); }; diff --git a/atom/browser/ui/views/menu_delegate.cc b/atom/browser/ui/views/menu_delegate.cc new file mode 100644 index 000000000000..4eb84815d3b4 --- /dev/null +++ b/atom/browser/ui/views/menu_delegate.cc @@ -0,0 +1,127 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/ui/views/menu_delegate.h" + +#include "atom/browser/ui/views/menu_bar.h" +#include "base/stl_util.h" +#include "ui/views/controls/button/menu_button.h" +#include "ui/views/controls/menu/menu_model_adapter.h" +#include "ui/views/controls/menu/menu_runner.h" +#include "ui/views/widget/widget.h" + +namespace atom { + +MenuDelegate::MenuDelegate(MenuBar* menu_bar) + : menu_bar_(menu_bar), + id_(-1), + items_(menu_bar_->GetItemCount()), + delegates_(menu_bar_->GetItemCount()) { +} + +MenuDelegate::~MenuDelegate() { + STLDeleteElements(&delegates_); +} + +void MenuDelegate::RunMenu(ui::MenuModel* model, views::MenuButton* button) { + gfx::Point screen_loc; + views::View::ConvertPointToScreen(button, &screen_loc); + // Subtract 1 from the height to make the popup flush with the button border. + gfx::Rect bounds(screen_loc.x(), screen_loc.y(), button->width(), + button->height() - 1); + + id_ = button->tag(); + views::MenuItemView* item = BuildMenu(model); + + menu_runner_.reset(new views::MenuRunner(item)); + views::MenuRunner::RunResult result = menu_runner_->RunMenuAt( + button->GetWidget()->GetTopLevelWidget(), + button, + bounds, + views::MenuItemView::TOPRIGHT, + ui::MENU_SOURCE_MOUSE, + views::MenuRunner::HAS_MNEMONICS | views::MenuRunner::CONTEXT_MENU); + if (result == views::MenuRunner::MENU_DELETED) + LOG(ERROR) << "Menu deleted when running"; +} + +views::MenuItemView* MenuDelegate::BuildMenu(ui::MenuModel* model) { + DCHECK_GE(id_, 0); + DCHECK_LT(id_, items_.size()); + + if (!items_[id_]) { + views::MenuModelAdapter* delegate = new views::MenuModelAdapter(model); + delegates_[id_] = delegate; + + views::MenuItemView* item = new views::MenuItemView(this); + delegate->BuildMenu(item); + items_[id_] = item; + } + + return items_[id_]; +} + +void MenuDelegate::ExecuteCommand(int id) { + delegate()->ExecuteCommand(id); +} + +void MenuDelegate::ExecuteCommand(int id, int mouse_event_flags) { + delegate()->ExecuteCommand(id, mouse_event_flags); +} + +bool MenuDelegate::IsTriggerableEvent(views::MenuItemView* source, + const ui::Event& e) { + return delegate()->IsTriggerableEvent(source, e); +} + +bool MenuDelegate::GetAccelerator(int id, ui::Accelerator* accelerator) { + return delegate()->GetAccelerator(id, accelerator); +} + +base::string16 MenuDelegate::GetLabel(int id) const { + return delegate()->GetLabel(id); +} + +const gfx::FontList* MenuDelegate::GetLabelFontList(int id) const { + return delegate()->GetLabelFontList(id); +} + +bool MenuDelegate::IsCommandEnabled(int id) const { + return delegate()->IsCommandEnabled(id); +} + +bool MenuDelegate::IsItemChecked(int id) const { + return delegate()->IsItemChecked(id); +} + +void MenuDelegate::SelectionChanged(views::MenuItemView* menu) { + delegate()->SelectionChanged(menu); +} + +void MenuDelegate::WillShowMenu(views::MenuItemView* menu) { + delegate()->WillShowMenu(menu); +} + +void MenuDelegate::WillHideMenu(views::MenuItemView* menu) { + delegate()->WillHideMenu(menu); +} + +views::MenuItemView* MenuDelegate::GetSiblingMenu( + views::MenuItemView* menu, + const gfx::Point& screen_point, + views::MenuItemView::AnchorPosition* anchor, + bool* has_mnemonics, + views::MenuButton** button) { + ui::MenuModel* model; + if (!menu_bar_->GetMenuButtonFromScreenPoint(screen_point, &model, button)) + return NULL; + + *anchor = views::MenuItemView::TOPLEFT; + *has_mnemonics = true; + + id_ = (*button)->tag(); + return BuildMenu(model); +} + +} // namespace atom diff --git a/atom/browser/ui/views/menu_delegate.h b/atom/browser/ui/views/menu_delegate.h new file mode 100644 index 000000000000..9d984bfc0731 --- /dev/null +++ b/atom/browser/ui/views/menu_delegate.h @@ -0,0 +1,76 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_VIEWS_MENU_DELEGATE_H_ +#define ATOM_BROWSER_UI_VIEWS_MENU_DELEGATE_H_ + +#include + +#include "ui/views/controls/menu/menu_delegate.h" + +namespace views { +class MenuModelAdapter; +class MenuRunner; +} + +namespace ui { +class MenuModel; +} + +namespace atom { + +class MenuBar; + +class MenuDelegate : public views::MenuDelegate { + public: + MenuDelegate(MenuBar* menu_bar); + virtual ~MenuDelegate(); + + void RunMenu(ui::MenuModel* model, views::MenuButton* button); + + protected: + // views::MenuDelegate: + virtual void ExecuteCommand(int id) OVERRIDE; + virtual void ExecuteCommand(int id, int mouse_event_flags) OVERRIDE; + virtual bool IsTriggerableEvent(views::MenuItemView* source, + const ui::Event& e) OVERRIDE; + virtual bool GetAccelerator(int id, + ui::Accelerator* accelerator) OVERRIDE; + virtual base::string16 GetLabel(int id) const OVERRIDE; + virtual const gfx::FontList* GetLabelFontList(int id) const OVERRIDE; + virtual bool IsCommandEnabled(int id) const OVERRIDE; + virtual bool IsItemChecked(int id) const OVERRIDE; + virtual void SelectionChanged(views::MenuItemView* menu) OVERRIDE; + virtual void WillShowMenu(views::MenuItemView* menu) OVERRIDE; + virtual void WillHideMenu(views::MenuItemView* menu) OVERRIDE; + virtual views::MenuItemView* GetSiblingMenu( + views::MenuItemView* menu, + const gfx::Point& screen_point, + views::MenuItemView::AnchorPosition* anchor, + bool* has_mnemonics, + views::MenuButton** button); + + private: + // Gets the cached menu item view from the model. + views::MenuItemView* BuildMenu(ui::MenuModel* model); + + // Returns delegate for current item. + views::MenuDelegate* delegate() const { return delegates_[id_]; } + + MenuBar* menu_bar_; + scoped_ptr menu_runner_; + + // Current item's id. + int id_; + // Cached menu items, managed by MenuRunner. + std::vector items_; + // Cached menu delegates for each menu item, managed by us. + std::vector delegates_; + + DISALLOW_COPY_AND_ASSIGN(MenuDelegate); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_VIEWS_MENU_DELEGATE_H_