summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoroshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-10-04 23:59:23 +0000
committeroshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-10-04 23:59:23 +0000
commit3a051a4b8a96fb3edd00ebddcd5eb48e8ecf4396 (patch)
tree7728a74b027dced0037b18f142329c10954bb7a0
parent23714db12751e6ba10a33a5110359af139d5566c (diff)
downloadchromium_src-3a051a4b8a96fb3edd00ebddcd5eb48e8ecf4396.zip
chromium_src-3a051a4b8a96fb3edd00ebddcd5eb48e8ecf4396.tar.gz
chromium_src-3a051a4b8a96fb3edd00ebddcd5eb48e8ecf4396.tar.bz2
DOMUI implementation of Menu2.
A few key design points. * Uses DOMView to render menu content. * MenuUI (subclass of DOMUI) uses GtkWiget tree to access WidgetGtk and MenuModel. Alternative way was to change TabContents::GetDOMUIForCurrentState() public and pass them throught it, but this seems to work fine. * Input/Focus is controlled by gtk_grab_add. Input is grabbed by the root widget, and then sent to each active menu widget via gtk_grab_add. * Menu gets shown after the renderer rendered the content (thus has some size) to avoid showing small/empty menu. Mouse processing is blocked until the menu is shown (see Dispatcher). A few issues that will be addressed in separate CL * RTL (will be after beta) * menu scroll. working on it now. * menu sometimes show scroll bar. (I just need to disable it) * unit test for native_menu_domui BUG=chromiun-os:6497 TEST=manual for now. all menu2 based meus should work as before (web pages's context menu, system menu, forward/back menu and several chromeos specific one) except for following two: * mnemonic should work now on context menus. * menu larger than screen does not scroll. I'll implement this in separate CL Review URL: http://codereview.chromium.org/3442018 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@61445 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/browser_resources.grd1
-rw-r--r--chrome/browser/chromeos/dom_ui/domui_menu_control.h60
-rw-r--r--chrome/browser/chromeos/dom_ui/menu_ui.cc345
-rw-r--r--chrome/browser/chromeos/dom_ui/menu_ui.h58
-rw-r--r--chrome/browser/chromeos/views/domui_menu_widget.cc294
-rw-r--r--chrome/browser/chromeos/views/domui_menu_widget.h104
-rw-r--r--chrome/browser/chromeos/views/menu_locator.cc285
-rw-r--r--chrome/browser/chromeos/views/menu_locator.h79
-rw-r--r--chrome/browser/chromeos/views/native_menu_domui.cc407
-rw-r--r--chrome/browser/chromeos/views/native_menu_domui.h130
-rw-r--r--chrome/browser/dom_ui/dom_ui_factory.cc3
-rw-r--r--chrome/browser/resources/menu.css63
-rw-r--r--chrome/browser/resources/menu.html16
-rw-r--r--chrome/browser/resources/menu.js474
-rw-r--r--chrome/chrome_browser.gypi9
-rw-r--r--chrome/common/url_constants.cc1
-rw-r--r--chrome/common/url_constants.h1
-rw-r--r--views/controls/menu/native_menu_gtk.cc4
18 files changed, 2334 insertions, 0 deletions
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index b42603d..b576252 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -77,6 +77,7 @@ without changes to the corresponding grd file. test -->
<include name="IDR_OS_CREDITS_HTML" file="resources\about_os_credits.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_SLIDESHOW_HTML" file="resources\slideshow.html" flattenhtml="true" type="BINDATA" />
<include name="IDR_TALK_APP_MANIFEST" file="resources\chat_manager\manifest.json" type="BINDATA" />
+ <include name="IDR_MENU_HTML" file="resources\menu.html" flattenhtml="true" type="BINDATA" />
</if>
</includes>
</release>
diff --git a/chrome/browser/chromeos/dom_ui/domui_menu_control.h b/chrome/browser/chromeos/dom_ui/domui_menu_control.h
new file mode 100644
index 0000000..d01ebea
--- /dev/null
+++ b/chrome/browser/chromeos/dom_ui/domui_menu_control.h
@@ -0,0 +1,60 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_DOM_UI_DOMUI_MENU_CONTROL_H_
+#define CHROME_BROWSER_CHROMEOS_DOM_UI_DOMUI_MENU_CONTROL_H_
+#pragma once
+
+namespace gfx {
+class Size;
+} // namespace gfx
+
+namespace menus {
+class MenuModel;
+} // namespace menus
+
+namespace chromeos {
+
+// DOMUIMenuControl class is used to control the UI counterpart of
+// a MenuModel. One instance of DOMUIMenuControl is created for each
+// MenuModel instance, that is, a submenu will have its own
+// DOMUIMenuControl.
+class DOMUIMenuControl {
+ public:
+ virtual ~DOMUIMenuControl() {}
+
+ // Returns the MenuModel associated with this control.
+ virtual menus::MenuModel* GetMenuModel() = 0;
+
+ // Activates an item in the |model| at |index|.
+ virtual void Activate(menus::MenuModel* model, int index) = 0;
+
+ // Close All menu window from root menu to leaf submenus.
+ virtual void CloseAll() = 0;
+
+ // Close the submenu (and all decendant submenus).
+ virtual void CloseSubmenu() = 0;
+
+ // Move the input to parent. Used in keyboard navigation.
+ virtual void MoveInputToParent() = 0;
+
+ // Move the input to submenu. Used in keyboard navigation.
+ virtual void MoveInputToSubmenu() = 0;
+
+ // Called when the menu page is loaded. This is used to call
+ // initialize function in JavaScript.
+ virtual void OnLoad() = 0;
+
+ // Open submenu using the submenu model at index in the model.
+ // The top coordinate of the selected menu is passed as |y_top|
+ // so that the submenu can be aligned to the selected item.
+ virtual void OpenSubmenu(int index, int y_top) =0;
+
+ // Sets the size of the menu.
+ virtual void SetSize(const gfx::Size& size) = 0;
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CHROMEOS_DOM_UI_DOMUI_MENU_CONTROL_H_
diff --git a/chrome/browser/chromeos/dom_ui/menu_ui.cc b/chrome/browser/chromeos/dom_ui/menu_ui.cc
new file mode 100644
index 0000000..c0c9856
--- /dev/null
+++ b/chrome/browser/chromeos/dom_ui/menu_ui.cc
@@ -0,0 +1,345 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/dom_ui/menu_ui.h"
+
+#include "app/menus/menu_model.h"
+#include "app/resource_bundle.h"
+#include "base/callback.h"
+#include "base/json/json_writer.h"
+#include "base/message_loop.h"
+#include "base/singleton.h"
+#include "base/string_number_conversions.h"
+#include "base/string_piece.h"
+#include "base/values.h"
+#include "base/weak_ptr.h"
+#include "chrome/browser/chrome_thread.h"
+#include "chrome/browser/chromeos/views/domui_menu_widget.h"
+#include "chrome/browser/chromeos/views/native_menu_domui.h"
+#include "chrome/browser/dom_ui/dom_ui_util.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/tab_contents/tab_contents_delegate.h"
+#include "chrome/common/url_constants.h"
+#include "grit/app_resources.h"
+#include "grit/browser_resources.h"
+#include "views/controls/menu/menu_config.h"
+#include "views/controls/menu/radio_button_image_gtk.h"
+#include "views/widget/widget_gtk.h"
+
+namespace {
+
+std::string GetImageUrlForRadioOn() {
+ return dom_ui_util::GetImageDataUrl(*views::GetRadioButtonImage(true));
+}
+
+std::string GetImageUrlForRadioOff() {
+ return dom_ui_util::GetImageDataUrl(*views::GetRadioButtonImage(false));
+}
+
+class MenuUIHTMLSource : public ChromeURLDataManager::DataSource {
+ public:
+ MenuUIHTMLSource();
+
+ // Called when the network layer has requested a resource underneath
+ // the path we registered.
+ virtual void StartDataRequest(const std::string& path,
+ bool is_off_the_record,
+ int request_id);
+ virtual std::string GetMimeType(const std::string&) const {
+ return "text/html";
+ }
+
+ private:
+ virtual ~MenuUIHTMLSource() {}
+
+ DISALLOW_COPY_AND_ASSIGN(MenuUIHTMLSource);
+};
+
+// The handler for Javascript messages related to the "system" view.
+class MenuHandler : public chromeos::MenuHandlerBase,
+ public base::SupportsWeakPtr<MenuHandler>,
+ public TabContentsDelegate {
+ public:
+ MenuHandler();
+ virtual ~MenuHandler();
+
+ // DOMMessageHandler implementation.
+ virtual DOMMessageHandler* Attach(DOMUI* dom_ui);
+ virtual void RegisterMessages();
+
+ private:
+ void HandleClick(const ListValue* values);
+ void HandleOpenSubmenu(const ListValue* values);
+ void HandleCloseSubmenu(const ListValue* values);
+ void HandleMoveInputToSubmenu(const ListValue* values);
+ void HandleMoveInputToParent(const ListValue* values);
+
+ // TabContentsDelegate implements:
+ virtual void UpdatePreferredSize(const gfx::Size& new_size);
+ virtual void LoadingStateChanged(TabContents* contents);
+
+ virtual void OpenURLFromTab(TabContents* source,
+ const GURL& url, const GURL& referrer,
+ WindowOpenDisposition disposition,
+ PageTransition::Type transition) {}
+ virtual void NavigationStateChanged(const TabContents* source,
+ unsigned changed_flags) {}
+ virtual void AddNewContents(TabContents* source,
+ TabContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {}
+
+ // TODO(oshima): Handle crash
+ virtual void ActivateContents(TabContents* contents) {}
+ virtual void DeactivateContents(TabContents* contents) {}
+ virtual void CloseContents(TabContents* source) {}
+ virtual void MoveContents(TabContents* source, const gfx::Rect& pos) {}
+ virtual bool IsPopup(const TabContents* source) { return false; }
+ virtual void ToolbarSizeChanged(TabContents* source, bool is_animating) {}
+ virtual void URLStarredChanged(TabContents* source, bool starred) {}
+ virtual void UpdateTargetURL(TabContents* source, const GURL& url) {}
+ virtual bool CanDownload(int request_id) { return false; }
+ virtual bool infobars_enabled() { return false; }
+ virtual bool ShouldEnablePreferredSizeNotifications() { return true; }
+ virtual bool CanReloadContents(TabContents* source) const { return false; }
+
+ // True if the page is loaded.
+ bool loaded_;
+
+ DISALLOW_COPY_AND_ASSIGN(MenuHandler);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// MenuUIHTMLSource
+//
+////////////////////////////////////////////////////////////////////////////////
+
+MenuUIHTMLSource::MenuUIHTMLSource()
+ : DataSource(chrome::kChromeUIMenu, MessageLoop::current()) {
+}
+
+void MenuUIHTMLSource::StartDataRequest(const std::string& path,
+ bool is_off_the_record,
+ int request_id) {
+ static const std::string menu_html =
+ chromeos::GetMenuUIHTMLSourceFromResource(IDR_MENU_HTML);
+
+ scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes);
+
+ // TODO(oshima): Eliminate boilerplate code. See http://crbug.com/57583 .
+ html_bytes->data.resize(menu_html.size());
+ std::copy(menu_html.begin(), menu_html.end(),
+ html_bytes->data.begin());
+ SendResponse(request_id, html_bytes);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// MenuHandler
+//
+////////////////////////////////////////////////////////////////////////////////
+MenuHandler::MenuHandler() : loaded_(false) {
+}
+
+MenuHandler::~MenuHandler() {
+}
+
+DOMMessageHandler* MenuHandler::Attach(DOMUI* dom_ui) {
+ DOMMessageHandler* handler = DOMMessageHandler::Attach(dom_ui);
+ dom_ui->tab_contents()->set_delegate(this);
+ return handler;
+}
+
+void MenuHandler::RegisterMessages() {
+ dom_ui_->RegisterMessageCallback(
+ "click",
+ NewCallback(this,
+ &MenuHandler::HandleClick));
+ dom_ui_->RegisterMessageCallback(
+ "open_submenu",
+ NewCallback(this,
+ &MenuHandler::HandleOpenSubmenu));
+ dom_ui_->RegisterMessageCallback(
+ "close_submenu",
+ NewCallback(this,
+ &MenuHandler::HandleCloseSubmenu));
+ dom_ui_->RegisterMessageCallback(
+ "move_to_submenu",
+ NewCallback(this,
+ &MenuHandler::HandleMoveInputToSubmenu));
+ dom_ui_->RegisterMessageCallback(
+ "move_to_parent",
+ NewCallback(this,
+ &MenuHandler::HandleMoveInputToParent));
+}
+
+void MenuHandler::HandleClick(const ListValue* values) {
+ CHECK_EQ(1U, values->GetSize());
+ std::string value;
+ bool success = values->GetString(0, &value);
+ DCHECK(success);
+
+ int index;
+ success = base::StringToInt(value, &index);
+ DCHECK(success) << " Faild to convert string to int " << value;
+
+ chromeos::DOMUIMenuControl* control = GetMenuControl();
+ if (control) {
+ menus::MenuModel* model = GetMenuModel();
+ DCHECK(model);
+ if (index < 0) {
+ control->CloseAll();
+ } else if (model->IsEnabledAt(index)) {
+ control->Activate(model, index);
+ }
+ }
+}
+
+void MenuHandler::HandleOpenSubmenu(const ListValue* values) {
+ std::string index_str;
+ values->GetString(0, &index_str);
+ std::string y_str;
+ values->GetString(1, &y_str);
+ int index;
+ int y;
+ base::StringToInt(index_str, &index);
+ base::StringToInt(y_str, &y);
+ chromeos::DOMUIMenuControl* control = GetMenuControl();
+ if (control)
+ control->OpenSubmenu(index, y);
+}
+
+void MenuHandler::HandleCloseSubmenu(const ListValue* values) {
+ chromeos::DOMUIMenuControl* control = GetMenuControl();
+ if (control)
+ control->CloseSubmenu();
+}
+
+void MenuHandler::HandleMoveInputToSubmenu(const ListValue* values) {
+ chromeos::DOMUIMenuControl* control = GetMenuControl();
+ if (control)
+ control->MoveInputToSubmenu();
+}
+
+void MenuHandler::HandleMoveInputToParent(const ListValue* values) {
+ chromeos::DOMUIMenuControl* control = GetMenuControl();
+ if (control)
+ control->MoveInputToParent();
+}
+
+void MenuHandler::UpdatePreferredSize(const gfx::Size& new_size) {
+ if (!loaded_)
+ return;
+ chromeos::DOMUIMenuControl* control = GetMenuControl();
+ if (control)
+ control->SetSize(new_size);
+}
+
+void MenuHandler::LoadingStateChanged(TabContents* contents) {
+ chromeos::DOMUIMenuControl* control = GetMenuControl();
+ if (control && !contents->is_loading()) {
+ loaded_ = true;
+ control->OnLoad();
+ }
+}
+
+} // namespace
+
+namespace chromeos {
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// MenuHandlerBase
+//
+////////////////////////////////////////////////////////////////////////////////
+
+chromeos::DOMUIMenuControl* MenuHandlerBase::GetMenuControl() {
+ DOMUIMenuWidget* widget =
+ chromeos::DOMUIMenuWidget::FindDOMUIMenuWidget(
+ dom_ui_->tab_contents()->GetNativeView());
+ if (widget)
+ return widget->domui_menu(); // NativeMenuDOMUI implements DOMUIMenuControl
+ else
+ return NULL;
+}
+
+menus::MenuModel* MenuHandlerBase::GetMenuModel() {
+ DOMUIMenuControl* control = GetMenuControl();
+ if (control)
+ return control->GetMenuModel();
+ else
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//
+// MenuUI
+//
+////////////////////////////////////////////////////////////////////////////////
+
+MenuUI::MenuUI(TabContents* contents) : DOMUI(contents) {
+ MenuHandler* handler = new MenuHandler();
+ AddMessageHandler((handler)->Attach(this));
+
+ ChromeThread::PostTask(
+ ChromeThread::IO, FROM_HERE,
+ NewRunnableMethod(
+ Singleton<ChromeURLDataManager>::get(),
+ &ChromeURLDataManager::AddDataSource,
+ make_scoped_refptr(CreateDataSource())));
+}
+
+ChromeURLDataManager::DataSource* MenuUI::CreateDataSource() {
+ return new MenuUIHTMLSource();
+}
+
+std::string GetMenuUIHTMLSourceFromResource(int res) {
+#define SET_INTEGER_PROPERTY(prop) \
+ value_config.SetInteger(#prop, menu_config.prop)
+
+ const base::StringPiece menu_html(
+ ResourceBundle::GetSharedInstance().GetRawDataResource(res));
+
+ const views::MenuConfig& menu_config = views::MenuConfig::instance();
+
+ DictionaryValue value_config;
+ value_config.SetString("radioOnUrl", GetImageUrlForRadioOn());
+ value_config.SetString("radioOffUrl", GetImageUrlForRadioOff());
+ value_config.SetString(
+ "arrowUrl", dom_ui_util::GetImageDataUrlFromResource(IDR_MENU_ARROW));
+ value_config.SetString(
+ "checkUrl", dom_ui_util::GetImageDataUrlFromResource(IDR_MENU_CHECK));
+
+ SET_INTEGER_PROPERTY(item_top_margin);
+ SET_INTEGER_PROPERTY(item_bottom_margin);
+ SET_INTEGER_PROPERTY(item_no_icon_top_margin);
+ SET_INTEGER_PROPERTY(item_no_icon_bottom_margin);
+ SET_INTEGER_PROPERTY(item_left_margin);
+ SET_INTEGER_PROPERTY(label_to_arrow_padding);
+ SET_INTEGER_PROPERTY(arrow_to_edge_padding);
+ SET_INTEGER_PROPERTY(icon_to_label_padding);
+ SET_INTEGER_PROPERTY(gutter_to_label);
+ SET_INTEGER_PROPERTY(check_width);
+ SET_INTEGER_PROPERTY(check_height);
+ SET_INTEGER_PROPERTY(radio_width);
+ SET_INTEGER_PROPERTY(radio_height);
+ SET_INTEGER_PROPERTY(arrow_height);
+ SET_INTEGER_PROPERTY(arrow_width);
+ SET_INTEGER_PROPERTY(gutter_width);
+ SET_INTEGER_PROPERTY(separator_height);
+ SET_INTEGER_PROPERTY(render_gutter);
+ SET_INTEGER_PROPERTY(show_mnemonics);
+ SET_INTEGER_PROPERTY(scroll_arrow_height);
+ SET_INTEGER_PROPERTY(label_to_accelerator_padding);
+
+ std::string json_config;
+ base::JSONWriter::Write(&value_config, false, &json_config);
+
+ return menu_html.as_string() +
+ "<script>init(" + json_config + ");</script>";
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/chromeos/dom_ui/menu_ui.h b/chrome/browser/chromeos/dom_ui/menu_ui.h
new file mode 100644
index 0000000..99cd7b7
--- /dev/null
+++ b/chrome/browser/chromeos/dom_ui/menu_ui.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_DOM_UI_MENU_UI_H_
+#define CHROME_BROWSER_CHROMEOS_DOM_UI_MENU_UI_H_
+#pragma once
+
+#include <string>
+
+#include "chrome/browser/dom_ui/chrome_url_data_manager.h"
+#include "chrome/browser/dom_ui/dom_ui.h"
+
+namespace menus {
+class MenuModel;
+} // namespace menus
+
+namespace chromeos {
+
+class DOMUIMenuControl;
+
+class MenuUI : public DOMUI {
+ public:
+ explicit MenuUI(TabContents* contents);
+
+ // Create HTML Data source for the menu. Extended menu
+ // implementation may provide its own menu implmentation.
+ virtual ChromeURLDataManager::DataSource* CreateDataSource();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MenuUI);
+};
+
+// Base class for MenuUI's DOMMessageHandler.
+class MenuHandlerBase : public DOMMessageHandler {
+ public:
+ MenuHandlerBase() : DOMMessageHandler() {}
+
+ // Returns the menu control that is associated with the
+ // MenuUI. This may return null when menu is being deleted.
+ DOMUIMenuControl* GetMenuControl();
+
+ // Returns the menu model for this menu ui.
+ // This may return null when menu is being deleted.
+ menus::MenuModel* GetMenuModel();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MenuHandlerBase);
+};
+
+// Returns the menu's html code given by the resource id with the code
+// to intialization the menu. The resource string should be pure code
+// and should not contain i18n string.
+std::string GetMenuUIHTMLSourceFromResource(int res);
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CHROMEOS_DOM_UI_MENU_UI_H_
diff --git a/chrome/browser/chromeos/views/domui_menu_widget.cc b/chrome/browser/chromeos/views/domui_menu_widget.cc
new file mode 100644
index 0000000..be6d309
--- /dev/null
+++ b/chrome/browser/chromeos/views/domui_menu_widget.cc
@@ -0,0 +1,294 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/views/domui_menu_widget.h"
+
+#include "chrome/browser/chromeos/views/menu_locator.h"
+#include "chrome/browser/chromeos/views/native_menu_domui.h"
+#include "chrome/browser/chromeos/wm_ipc.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/render_widget_host_view.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/views/dom_view.h"
+#include "cros/chromeos_wm_ipc_enums.h"
+#include "gfx/canvas_skia.h"
+#include "googleurl/src/gurl.h"
+#include "third_party/skia/include/effects/SkGradientShader.h"
+#include "views/border.h"
+#include "views/layout_manager.h"
+#include "views/widget/root_view.h"
+
+namespace {
+
+// Colors for menu's graident background.
+const SkColor kMenuStartColor = SK_ColorWHITE;
+const SkColor kMenuEndColor = 0xFFEEEEEE;
+
+// Rounded border for menu. This draws three types of rounded border,
+// for context menu, dropdown menu and submenu. Please see
+// menu_locator.cc for details.
+class RoundedBorder : public views::Border {
+ public:
+ explicit RoundedBorder(chromeos::MenuLocator* locator)
+ : menu_locator_(locator) {
+ }
+
+ private:
+ // views::Border implementatios.
+ virtual void Paint(const views::View& view, gfx::Canvas* canvas) const {
+ const SkScalar* corners = menu_locator_->GetCorners();
+ // The menu is in off screen so no need to draw corners.
+ if (!corners)
+ return;
+ int w = view.width();
+ int h = view.height();
+ SkRect rect = {0, 0, w, h};
+ SkPath path;
+ path.addRoundRect(rect, corners);
+ SkPaint paint;
+ paint.setStyle(SkPaint::kFill_Style);
+ paint.setFlags(SkPaint::kAntiAlias_Flag);
+ SkPoint p[2] = { {0, 0}, {0, h} };
+ SkColor colors[2] = {kMenuStartColor, kMenuEndColor};
+ SkShader* s = SkGradientShader::CreateLinear(
+ p, colors, NULL, 2, SkShader::kClamp_TileMode, NULL);
+ paint.setShader(s);
+ // Need to unref shader, otherwise never deleted.
+ s->unref();
+ canvas->AsCanvasSkia()->drawPath(path, paint);
+ }
+
+ virtual void GetInsets(gfx::Insets* insets) const {
+ DCHECK(insets);
+ menu_locator_->GetInsets(insets);
+ }
+
+ chromeos::MenuLocator* menu_locator_; // not owned
+
+ DISALLOW_COPY_AND_ASSIGN(RoundedBorder);
+};
+
+class InsetsLayout : public views::LayoutManager {
+ public:
+ InsetsLayout() : views::LayoutManager() {}
+
+ private:
+ // views::LayoutManager implementatios.
+ virtual void Layout(views::View* host) {
+ if (host->GetChildViewCount() == 0)
+ return;
+ gfx::Insets insets = host->GetInsets();
+ views::View* view = host->GetChildViewAt(0);
+
+ view->SetBounds(insets.left(), insets.top(),
+ host->width() - insets.width(),
+ host->height() - insets.height());
+ }
+
+ virtual gfx::Size GetPreferredSize(views::View* host) {
+ DCHECK(host->GetChildViewCount() == 1);
+ gfx::Insets insets = host->GetInsets();
+ gfx::Size size = host->GetChildViewAt(0)->GetPreferredSize();
+ return gfx::Size(size.width() + insets.width(),
+ size.height() + insets.height());
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(InsetsLayout);
+};
+
+// A gtk widget key used to test if a given WidgetGtk instance is
+// DOMUIMenuWidgetKey.
+const char* kDOMUIMenuWidgetKey = "__DOMUI_MENU_WIDGET__";
+
+} // namespace
+
+namespace chromeos {
+
+// static
+DOMUIMenuWidget* DOMUIMenuWidget::FindDOMUIMenuWidget(gfx::NativeView native) {
+ DCHECK(native);
+ native = gtk_widget_get_toplevel(native);
+ if (!native)
+ return NULL;
+ return static_cast<chromeos::DOMUIMenuWidget*>(
+ g_object_get_data(G_OBJECT(native), kDOMUIMenuWidgetKey));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DOMUIMenuWidget public:
+
+DOMUIMenuWidget::DOMUIMenuWidget(chromeos::NativeMenuDOMUI* domui_menu,
+ bool root)
+ : views::WidgetGtk(views::WidgetGtk::TYPE_POPUP),
+ domui_menu_(domui_menu),
+ dom_view_(NULL),
+ did_pointer_grab_(false),
+ is_root_(root) {
+ DCHECK(domui_menu_);
+ MakeTransparent();
+}
+
+DOMUIMenuWidget::~DOMUIMenuWidget() {
+}
+
+void DOMUIMenuWidget::Init(gfx::NativeView parent, const gfx::Rect& bounds) {
+ WidgetGtk::Init(parent, bounds);
+ gtk_window_set_destroy_with_parent(GTK_WINDOW(GetNativeView()), TRUE);
+ gtk_window_set_type_hint(GTK_WINDOW(GetNativeView()),
+ GDK_WINDOW_TYPE_HINT_MENU);
+ g_object_set_data(G_OBJECT(GetNativeView()), kDOMUIMenuWidgetKey, this);
+}
+
+void DOMUIMenuWidget::Hide() {
+ ReleaseGrab();
+ WidgetGtk::Hide();
+}
+
+void DOMUIMenuWidget::Close() {
+ // Detach the domui_menu_ which is being deleted.
+ domui_menu_ = NULL;
+ views::WidgetGtk::Close();
+}
+
+void DOMUIMenuWidget::ReleaseGrab() {
+ WidgetGtk::ReleaseGrab();
+ if (did_pointer_grab_) {
+ did_pointer_grab_ = false;
+ gdk_pointer_ungrab(GDK_CURRENT_TIME);
+
+ ClearGrabWidget();
+ }
+}
+
+gboolean DOMUIMenuWidget::OnGrabBrokeEvent(GtkWidget* widget,
+ GdkEvent* event) {
+ did_pointer_grab_ = false;
+ Hide();
+ return WidgetGtk::OnGrabBrokeEvent(widget, event);
+}
+
+void DOMUIMenuWidget::OnSizeAllocate(GtkWidget* widget,
+ GtkAllocation* allocation) {
+ views::WidgetGtk::OnSizeAllocate(widget, allocation);
+ // Adjust location when menu gets resized.
+ gfx::Rect bounds;
+ GetBounds(&bounds, false);
+ // Don't move until the menu gets contents.
+ if (bounds.height() >= 2)
+ menu_locator_->Move(this);
+}
+
+gboolean MapToFocus(GtkWidget* widget, GdkEvent* event, gpointer data) {
+ DOMUIMenuWidget* menu_widget = DOMUIMenuWidget::FindDOMUIMenuWidget(widget);
+ if (menu_widget)
+ menu_widget->EnableInput(false);
+ return true;
+}
+
+void DOMUIMenuWidget::EnableInput(bool select_item) {
+ DCHECK(dom_view_);
+ DCHECK(dom_view_->tab_contents()->render_view_host());
+ DCHECK(dom_view_->tab_contents()->render_view_host()->view());
+ GtkWidget* target =
+ dom_view_->tab_contents()->render_view_host()->view()->GetNativeView();
+ DCHECK(target);
+ // Skip if the widget already own the input.
+ if (gtk_grab_get_current() == target)
+ return;
+
+ ClearGrabWidget();
+
+ if (!GTK_WIDGET_REALIZED(target)) {
+ // Try again if the widget is not yet realized.
+ // This happens only for root which tries to open & grab input.
+ DCHECK(is_root_);
+ DCHECK(!select_item);
+ g_signal_connect(G_OBJECT(target), "map-event",
+ G_CALLBACK(&MapToFocus), NULL);
+ return;
+ }
+
+ gtk_grab_add(target);
+ dom_view_->tab_contents()->Focus();
+ if (select_item) {
+ ExecuteJavascript(L"selectItem()");
+ }
+}
+
+void DOMUIMenuWidget::ExecuteJavascript(const std::wstring& script) {
+ // Don't exeute there is no DOMView associated. This is fine because
+ // 1) selectItem make sense only when DOMView is associated.
+ // 2) updateModel will be called again when a DOMView is created/assigned.
+ if (dom_view_ == NULL)
+ return;
+
+ DCHECK(dom_view_->tab_contents()->render_view_host());
+ dom_view_->tab_contents()->render_view_host()->
+ ExecuteJavascriptInWebFrame(std::wstring(), script);
+}
+
+void DOMUIMenuWidget::ShowAt(chromeos::MenuLocator* locator) {
+ DCHECK(domui_menu_);
+ menu_locator_.reset(locator);
+ if (!dom_view_) {
+ dom_view_ = new DOMView();
+ dom_view_->Init(domui_menu_->GetProfile(), NULL);
+ // TODO(oshima): remove extra view to draw rounded corner.
+ views::View* container = new views::View();
+ container->AddChildView(dom_view_);
+ container->set_border(new RoundedBorder(locator));
+ container->SetLayoutManager(new InsetsLayout());
+ SetContentsView(container);
+ dom_view_->LoadURL(GURL("chrome://menu"));
+ } else {
+ domui_menu_->UpdateStates();
+ dom_view_->GetParent()->set_border(new RoundedBorder(locator));
+ menu_locator_->Move(this);
+ }
+ Show();
+
+ // The pointer grab is captured only on the top level menu,
+ // all mouse event events are delivered to submenu using gtk_add_grab.
+ if (is_root_) {
+ CaptureGrab();
+ }
+}
+
+void DOMUIMenuWidget::SetSize(const gfx::Size& new_size) {
+ DCHECK(domui_menu_);
+ // Ignore the empty new_size request which is called when
+ // menu.html is loaded.
+ if (new_size.IsEmpty()) return;
+
+ menu_locator_->SetBounds(this, new_size);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// DOMUIMenuWidget private:
+
+void DOMUIMenuWidget::CaptureGrab() {
+ // Release the current grab.
+ ClearGrabWidget();
+
+ // NOTE: we do this to ensure we get mouse events from other apps, a grab
+ // done with gtk_grab_add doesn't get events from other apps.
+ GdkGrabStatus grab_status =
+ gdk_pointer_grab(window_contents()->window, FALSE,
+ static_cast<GdkEventMask>(
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK),
+ NULL, NULL, GDK_CURRENT_TIME);
+ did_pointer_grab_ = (grab_status == GDK_GRAB_SUCCESS);
+ DCHECK(did_pointer_grab_);
+
+ EnableInput(false /* no selection */);
+}
+
+void DOMUIMenuWidget::ClearGrabWidget() {
+ GtkWidget* grab_widget;
+ while ((grab_widget = gtk_grab_get_current()))
+ gtk_grab_remove(grab_widget);
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/chromeos/views/domui_menu_widget.h b/chrome/browser/chromeos/views/domui_menu_widget.h
new file mode 100644
index 0000000..ed59577
--- /dev/null
+++ b/chrome/browser/chromeos/views/domui_menu_widget.h
@@ -0,0 +1,104 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_VIEWS_DOMUI_MENU_WIDGET_H_
+#define CHROME_BROWSER_CHROMEOS_VIEWS_DOMUI_MENU_WIDGET_H_
+#pragma once
+
+#include <string>
+
+#include "views/widget/widget_gtk.h"
+
+class DOMView;
+
+namespace chromeos {
+
+class MenuLocator;
+class NativeMenuDOMUI;
+
+// DOMUIMenuWidget is a window widget for DOMUI based menu.
+class DOMUIMenuWidget : public views::WidgetGtk {
+ public:
+ // Create a Window for the NativeMenuDMOUI. |root| specifies if
+ // the menu is root menu.
+ DOMUIMenuWidget(NativeMenuDOMUI* domui_menu, bool root);
+ virtual ~DOMUIMenuWidget();
+
+ // WidgetGtk overrides:
+ virtual void Init(gfx::NativeView parent, const gfx::Rect& bounds);
+ virtual void Hide();
+ virtual void Close();
+ virtual void ReleaseGrab();
+ virtual gboolean OnGrabBrokeEvent(GtkWidget* widget, GdkEvent* event);
+ virtual void OnSizeAllocate(GtkWidget* widget, GtkAllocation* allocation);
+
+ // Returns NativeMenuDOMUI that owns this widget.
+ NativeMenuDOMUI* domui_menu() const {
+ return domui_menu_;
+ }
+
+ // Returns true if the menu widget is a root.
+ bool is_root() const {
+ return is_root_;
+ }
+
+ // Returns true if the menu widget has input grab.
+ bool did_pointer_grab() const {
+ return did_pointer_grab_;
+ }
+
+ // Tell the gtk to send all input events (mouse, keyboard) to this
+ // Widget. If |selectItem| is true, it highlights the selected item
+ // (or select 1st selectable item if none is selected).
+ void EnableInput(bool select_item);
+
+ // Executes given |javascript|.
+ void ExecuteJavascript(const std::wstring& javascript);
+
+ // Show the menu using |locator|. Ownership of locator is transferred
+ // to this widget.
+ void ShowAt(MenuLocator* locator);
+
+ // Updates the size
+ void SetSize(const gfx::Size& new_size);
+
+ // Returns the menu locator owned by this widget.
+ const MenuLocator* menu_locator() const {
+ return menu_locator_.get();
+ }
+
+ // Returns DOMUIMenuWidget that contains given native. This returns
+ // NULL if not found.
+ static DOMUIMenuWidget* FindDOMUIMenuWidget(gfx::NativeView native);
+
+ private:
+ // Capture the X pointer grab. This also enables input on the widget by
+ // calling EnableInput(false).
+ void CaptureGrab();
+
+ // Clears GTK grab.
+ void ClearGrabWidget();
+
+ // NativeMenu object that owns this widget.
+ NativeMenuDOMUI* domui_menu_;
+
+ // DOMView to render the menu contents.
+ DOMView* dom_view_;
+
+ // MenuLocator that controls the position of this menu widget.
+ scoped_ptr<chromeos::MenuLocator> menu_locator_;
+
+ // True if the widget has pointer grab.
+ bool did_pointer_grab_;
+
+ // True if the widget is for root menu (very first menu in
+ // submenu chain).
+ bool is_root_;
+
+ DISALLOW_COPY_AND_ASSIGN(DOMUIMenuWidget);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CHROMEOS_VIEWS_DOMUI_MENU_WIDGET_H_
diff --git a/chrome/browser/chromeos/views/menu_locator.cc b/chrome/browser/chromeos/views/menu_locator.cc
new file mode 100644
index 0000000..bf995e7
--- /dev/null
+++ b/chrome/browser/chromeos/views/menu_locator.cc
@@ -0,0 +1,285 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/views/menu_locator.h"
+
+#include "base/logging.h"
+#include "gfx/point.h"
+#include "gfx/rect.h"
+#include "gfx/insets.h"
+#include "views/screen.h"
+#include "views/widget/widget.h"
+
+namespace {
+
+using views::Widget;
+
+// Menu's corner radious.
+const int kMenuCornerRadius = 4;
+const int kSubmenuOverlapPx = 1;
+
+gfx::Rect GetBoundsOf(const views::Widget* widget) {
+ gfx::Rect bounds;
+ widget->GetBounds(&bounds, false);
+ return bounds;
+}
+
+// Returns the Rect of the screen that contains the point (x, y).
+gfx::Rect GetScreenRectAt(int x, int y) {
+ return views::Screen::GetMonitorAreaNearestPoint(gfx::Point(x, y));
+}
+
+// MenuLocator for dropdown menu.
+class DropDownMenuLocator : public chromeos::MenuLocator {
+ public:
+ explicit DropDownMenuLocator(const gfx::Point& origin)
+ : origin_(origin) {
+ }
+
+ private:
+ virtual SubmenuDirection GetSubmenuDirection() const {
+ return DEFAULT;
+ }
+
+ virtual void Move(Widget* widget) {
+ gfx::Rect bounds;
+ widget->GetBounds(&bounds, false);
+ widget->SetBounds(ComputeBounds(bounds.size()));
+ }
+
+ virtual void SetBounds(Widget* widget, const gfx::Size& size) {
+ gfx::Size new_size(size);
+ new_size.Enlarge(0, kMenuCornerRadius);
+ widget->SetBounds(ComputeBounds(new_size));
+ }
+
+ gfx::Rect ComputeBounds(const gfx::Size& size) {
+ // Menu has to be shown above the button, which is not currently
+ // possible with Menu2. I'll update Menu2 and this code
+ // once this change is landed.
+ gfx::Rect screen_rect = GetScreenRectAt(origin_.x(), origin_.y());
+ int x = origin_.x() - size.width();
+ int y = origin_.y();
+ if (x + size.width() > screen_rect.right()) {
+ x = screen_rect.right() - size.width();
+ }
+ if (y + size.height() > screen_rect.bottom()) {
+ y = screen_rect.bottom() - size.height();
+ }
+ return gfx::Rect(x, y, size.width(), size.height());
+ }
+
+ virtual void GetInsets(gfx::Insets* insets) const {
+ insets->Set(0, 0, kMenuCornerRadius, 0);
+ }
+
+ virtual const SkScalar* GetCorners() const {
+ static const SkScalar corners[] = {
+ 0, 0,
+ 0, 0,
+ kMenuCornerRadius, kMenuCornerRadius,
+ kMenuCornerRadius, kMenuCornerRadius,
+ };
+ return corners;
+ }
+
+ gfx::Point origin_;
+
+ DISALLOW_COPY_AND_ASSIGN(DropDownMenuLocator);
+};
+
+// MenuLocator for context menu.
+class ContextMenuLocator : public chromeos::MenuLocator {
+ public:
+ explicit ContextMenuLocator(const gfx::Point& origin)
+ : origin_(origin) {
+ }
+
+ private:
+ virtual SubmenuDirection GetSubmenuDirection() const {
+ return DEFAULT;
+ }
+
+ virtual void Move(Widget* widget) {
+ gfx::Rect bounds;
+ widget->GetBounds(&bounds, false);
+ widget->SetBounds(ComputeBounds(bounds.size()));
+ }
+
+ virtual void SetBounds(Widget* widget, const gfx::Size& size) {
+ gfx::Size new_size(size);
+ new_size.Enlarge(0, kMenuCornerRadius * 2);
+ widget->SetBounds(ComputeBounds(new_size));
+ }
+
+ gfx::Rect ComputeBounds(const gfx::Size& size) {
+ gfx::Rect screen_rect = GetScreenRectAt(origin_.x(), origin_.y());
+ int x = origin_.x();
+ int y = origin_.y();
+ if (x + size.width() > screen_rect.right()) {
+ x = screen_rect.right() - size.width();
+ }
+ if (y + size.height() > screen_rect.bottom()) {
+ y = screen_rect.bottom() - size.height();
+ }
+ return gfx::Rect(x, y, size.width(), size.height());
+ }
+
+ virtual const SkScalar* GetCorners() const {
+ static const SkScalar corners[] = {
+ kMenuCornerRadius, kMenuCornerRadius,
+ kMenuCornerRadius, kMenuCornerRadius,
+ kMenuCornerRadius, kMenuCornerRadius,
+ kMenuCornerRadius, kMenuCornerRadius,
+ };
+ return corners;
+ }
+
+ virtual void GetInsets(gfx::Insets* insets) const {
+ insets->Set(kMenuCornerRadius, 0, kMenuCornerRadius, 0);
+ }
+
+ gfx::Point origin_;
+
+ DISALLOW_COPY_AND_ASSIGN(ContextMenuLocator);
+};
+
+// MenuLocator for submenu.
+class SubMenuLocator : public chromeos::MenuLocator {
+ public:
+ SubMenuLocator(const views::Widget* parent,
+ MenuLocator::SubmenuDirection parent_direction,
+ int y)
+ : parent_rect_(GetBoundsOf(parent)),
+ parent_direction_(parent_direction),
+ root_y_(parent_rect_.y() + y),
+ corners_(NULL),
+ direction_(DEFAULT) {
+ }
+
+ private:
+ virtual SubmenuDirection GetSubmenuDirection() const {
+ return direction_;
+ }
+
+ virtual void Move(Widget* widget) {
+ gfx::Rect bounds;
+ widget->GetBounds(&bounds, false);
+ widget->SetBounds(ComputeBounds(bounds.size()));
+ }
+
+ virtual void SetBounds(Widget* widget, const gfx::Size& size) {
+ gfx::Size new_size(size);
+ new_size.Enlarge(0, kMenuCornerRadius * 2);
+ widget->SetBounds(ComputeBounds(new_size));
+ }
+
+ virtual const SkScalar* GetCorners() const {
+ return corners_;
+ }
+
+ virtual void GetInsets(gfx::Insets* insets) const {
+ insets->Set(kMenuCornerRadius, 0, kMenuCornerRadius, 0);
+ }
+
+ // Rounded corner definitions for right/left attached submenu.
+ static const SkScalar kRightCorners[];
+ static const SkScalar kLeftCorners[];
+
+ gfx::Rect ComputeBounds(const gfx::Size& size) {
+ gfx::Rect screen_rect = GetScreenRectAt(parent_rect_.x(), root_y_);
+ SubmenuDirection direction = parent_direction_;
+ if (direction == DEFAULT) {
+ direction = RIGHT; // TOOD(oshima): support RTL
+ }
+ // Adjust Y to fit the screen.
+ int y = root_y_;
+ if (root_y_ + size.height() > screen_rect.bottom()) {
+ y = screen_rect.bottom() - size.height();
+ }
+ // Decide the attachment.
+ int x = direction == RIGHT ?
+ ComputeXToRight(screen_rect, size) :
+ ComputeXToLeft(screen_rect, size);
+ return gfx::Rect(x, y, size.width(), size.height());
+ }
+
+ int ComputeXToRight(const gfx::Rect& screen_rect, const gfx::Size& size) {
+ if (parent_rect_.right() + size.width() > screen_rect.right()) {
+ corners_ = kLeftCorners;
+ direction_ = LEFT;
+ return parent_rect_.x() - size.width() + kSubmenuOverlapPx;
+ } else {
+ corners_ = kRightCorners;
+ direction_ = RIGHT;
+ return parent_rect_.right() - kSubmenuOverlapPx;
+ }
+ }
+
+ int ComputeXToLeft(const gfx::Rect& screen_rect, const gfx::Size& size) {
+ if (parent_rect_.x() - size.width() < screen_rect.x()) {
+ corners_ = kRightCorners;
+ direction_ = RIGHT;
+ return parent_rect_.right() - kSubmenuOverlapPx;
+ } else {
+ corners_ = kLeftCorners;
+ direction_ = LEFT;
+ return parent_rect_.x() - size.width() + kSubmenuOverlapPx;
+ }
+ }
+
+ const gfx::Rect parent_rect_;
+
+ const MenuLocator::SubmenuDirection parent_direction_;
+
+ const int root_y_;
+
+ SkScalar const* corners_;
+
+ // The direction the this menu is attached to its parent. Submenu may still
+ // choose different direction if there is no spece for that direction
+ // (2nd turnaround).
+ SubmenuDirection direction_;
+
+ DISALLOW_COPY_AND_ASSIGN(SubMenuLocator);
+};
+
+// Rounded corners of the submenu attached to right side.
+const SkScalar SubMenuLocator::kRightCorners[] = {
+ 0, 0,
+ kMenuCornerRadius, kMenuCornerRadius,
+ kMenuCornerRadius, kMenuCornerRadius,
+ kMenuCornerRadius, kMenuCornerRadius,
+};
+
+// Rounded corners of the submenu attached to left side.
+const SkScalar SubMenuLocator::kLeftCorners[] = {
+ kMenuCornerRadius, kMenuCornerRadius,
+ 0, 0,
+ kMenuCornerRadius, kMenuCornerRadius,
+ kMenuCornerRadius, kMenuCornerRadius,
+};
+
+
+} // namespace
+
+namespace chromeos {
+
+// static
+MenuLocator* MenuLocator::CreateDropDownMenuLocator(const gfx::Point& p) {
+ return new DropDownMenuLocator(p);
+}
+
+MenuLocator* MenuLocator::CreateContextMenuLocator(const gfx::Point& p) {
+ return new ContextMenuLocator(p);
+}
+
+MenuLocator* MenuLocator::CreateSubMenuLocator(
+ const views::Widget* parent,
+ MenuLocator::SubmenuDirection parent_direction,
+ int y) {
+ return new SubMenuLocator(parent, parent_direction, y);
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/chromeos/views/menu_locator.h b/chrome/browser/chromeos/views/menu_locator.h
new file mode 100644
index 0000000..aaedf27
--- /dev/null
+++ b/chrome/browser/chromeos/views/menu_locator.h
@@ -0,0 +1,79 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_VIEWS_MENU_LOCATOR_H_
+#define CHROME_BROWSER_CHROMEOS_VIEWS_MENU_LOCATOR_H_
+#pragma once
+
+#include "third_party/skia/include/core/SkScalar.h"
+
+namespace gfx {
+class Insets;
+class Point;
+class Size;
+} // namespace gfx
+
+namespace views {
+class Widget;
+} // namespace views
+
+namespace chromeos {
+
+// MenuLocator class contorls where the menu will be placed and
+// which corners are rounded.
+// TODO(oshima): support RTL.
+class MenuLocator {
+ public:
+ enum SubmenuDirection {
+ DEFAULT, // default direction.
+ RIGHT, // submenu should grow to right. (not used now. reserved for RTL)
+ LEFT, // submenu should grow to left.
+ };
+
+ virtual ~MenuLocator() {}
+
+ // Returns the direction that submenu should grow.
+ virtual SubmenuDirection GetSubmenuDirection() const = 0;
+
+ // Move the widget to the right position.
+ virtual void Move(views::Widget* widget) = 0;
+
+ // Resize and move the widget to the right position.
+ virtual void SetBounds(views::Widget* widget,
+ const gfx::Size& size) = 0;
+
+ // Returns the 8 length array of SkScalar that represents 4 corner
+ // radious for each menu type. The objects are owned by the locator
+ // and should not be deleted. This can be null when SubMenu is
+ // still off screen (not visible).
+ virtual const SkScalar* GetCorners() const = 0;
+
+ // Returns the insets to give space to draw rounded corners.
+ virtual void GetInsets(gfx::Insets* insets) const = 0;
+
+ // Returns the menu locator for dropdown menu. The menu will
+ // positioned so that the top right corner is given by "point".
+ // Only bottom corners are rounded.
+ static MenuLocator* CreateDropDownMenuLocator(const gfx::Point& point);
+
+ // Returns the menu locator for context menu. The menu will
+ // positioned so that the top left corner is given by "point" (in
+ // LTR). All 4 corners are rounded.
+ static MenuLocator* CreateContextMenuLocator(const gfx::Point& point);
+
+ // Returns the menu locator for submenu menu. The menu will
+ // positioned at Y and on the right side of the widget, or on the
+ // left if there is no enough spaceon the right side. Once it changes the
+ // derection, all subsequent submenu should be positioned to the same
+ // direction given by |parent_direction|. 3 corners are
+ // rounded except for the corner that is attached to the widget.
+ static MenuLocator* CreateSubMenuLocator(
+ const views::Widget* widget,
+ MenuLocator::SubmenuDirection parent_direction,
+ int y);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CHROMEOS_VIEWS_MENU_LOCATOR_H_
diff --git a/chrome/browser/chromeos/views/native_menu_domui.cc b/chrome/browser/chromeos/views/native_menu_domui.cc
new file mode 100644
index 0000000..1cd68f1
--- /dev/null
+++ b/chrome/browser/chromeos/views/native_menu_domui.cc
@@ -0,0 +1,407 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/chromeos/views/native_menu_domui.h"
+
+#include <string>
+
+#include "app/menus/menu_model.h"
+#include "base/json/json_writer.h"
+#include "base/message_loop.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/browser_list.h"
+#include "chrome/browser/chromeos/views/domui_menu_widget.h"
+#include "chrome/browser/chromeos/views/menu_locator.h"
+#include "chrome/browser/dom_ui/dom_ui_util.h"
+#include "chrome/browser/profile_manager.h"
+#include "gfx/favicon_size.h"
+#include "gfx/font.h"
+#include "gfx/rect.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "views/controls/menu/menu_2.h"
+#include "views/controls/menu/menu_config.h"
+
+namespace {
+
+// Returns true if the menu item type specified can be executed as a command.
+bool MenuTypeCanExecute(menus::MenuModel::ItemType type) {
+ return type == menus::MenuModel::TYPE_COMMAND ||
+ type == menus::MenuModel::TYPE_CHECK ||
+ type == menus::MenuModel::TYPE_RADIO;
+}
+
+// A utility function that generates css font property from gfx::Font.
+std::wstring GetFontShorthand(const gfx::Font* font) {
+ std::wstring out;
+ if (font == NULL) {
+ font = &(views::MenuConfig::instance().font);
+ }
+ if (font->GetStyle() & gfx::Font::BOLD) {
+ out.append(L"bold ");
+ }
+ if (font->GetStyle() & gfx::Font::ITALIC) {
+ out.append(L"italic ");
+ }
+ if (font->GetStyle() & gfx::Font::UNDERLINED) {
+ out.append(L"underline ");
+ }
+
+ // TODO(oshima): The font size from gfx::Font is too small when
+ // used in webkit. Figure out the reason.
+ out.append(ASCIIToWide(base::IntToString(font->GetFontSize() + 4)));
+ out.append(L"px/");
+ out.append(ASCIIToWide(base::IntToString(
+ std::max(kFavIconSize, font->GetHeight()))));
+ out.append(L"px \"");
+ out.append(font->GetFontName());
+ out.append(L"\",sans-serif");
+ return out;
+}
+
+// Currently opened menu. See RunMenuAt for reason why we need this.
+chromeos::NativeMenuDOMUI* current_ = NULL;
+
+} // namespace
+
+namespace chromeos {
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeMenuDOMUI, public:
+
+NativeMenuDOMUI::NativeMenuDOMUI(menus::MenuModel* menu_model, bool root)
+ : parent_(NULL),
+ submenu_(NULL),
+ model_(menu_model),
+ menu_widget_(NULL),
+ menu_shown_(false),
+ activated_menu_(NULL),
+ activated_index_(-1),
+ menu_action_(MENU_ACTION_NONE) {
+ menu_widget_ = new DOMUIMenuWidget(this, root);
+ // Set the initial location off the screen not to show small
+ // window with dropshadow.
+ menu_widget_->Init(NULL, gfx::Rect(-10000, -10000, 1, 1));
+}
+
+NativeMenuDOMUI::~NativeMenuDOMUI() {
+ DCHECK(!menu_shown_) << "Deleting while the menu is shown";
+ if (menu_widget_) {
+ menu_widget_->Close();
+ menu_widget_ = NULL;
+ }
+ parent_ = NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeMenuDOMUI, MenuWrapper implementation:
+
+void NativeMenuDOMUI::RunMenuAt(const gfx::Point& point, int alignment) {
+ if (current_ != NULL) {
+ // This happens when there is a nested task to show menu, which is
+ // executed after menu is open. Since we need to enable nested task,
+ // this condition has to be handled here.
+ return;
+ }
+ current_ = this;
+ bool context = false;
+
+ // TODO(oshima): This is quick hack to check if it's context menu. (in rtl)
+ // Fix this once we migrated.
+ if (alignment == views::Menu2::ALIGN_TOPLEFT) {
+ context = true;
+ }
+
+ activated_menu_ = NULL;
+ activated_index_ = -1;
+ menu_action_ = MENU_ACTION_NONE;
+
+ MenuLocator* locator = context ?
+ MenuLocator::CreateContextMenuLocator(point) :
+ MenuLocator::CreateDropDownMenuLocator(point);
+ ShowAt(locator);
+ FOR_EACH_OBSERVER(views::MenuListener, listeners_, OnMenuOpened());
+ DCHECK(!menu_shown_);
+ menu_shown_ = true;
+
+ // We need to turn on nestable tasks as a renderer uses tasks internally.
+ // Without this, renderer cannnot finish loading page.
+ bool nestable = MessageLoopForUI::current()->NestableTasksAllowed();
+ MessageLoopForUI::current()->SetNestableTasksAllowed(true);
+ MessageLoopForUI::current()->Run(this);
+ MessageLoopForUI::current()->SetNestableTasksAllowed(nestable);
+ if (menu_shown_) {
+ // If this happens it means we haven't yet gotten the hide signal and
+ // someone else quit the message loop on us.
+ NOTREACHED();
+ menu_shown_ = false;
+ }
+ menu_widget_->Hide();
+ // Close All submenus.
+ submenu_.reset();
+ current_ = NULL;
+ ProcessActivate();
+}
+
+void NativeMenuDOMUI::CancelMenu() {
+ Hide();
+}
+
+void NativeMenuDOMUI::Rebuild() {
+ activated_menu_ = NULL;
+ DictionaryValue model;
+ ListValue* items = new ListValue();
+ model.Set("items", items);
+ bool has_icon = false;
+ for (int index = 0; index < model_->GetItemCount(); ++index) {
+ menus::MenuModel::ItemType type = model_->GetTypeAt(index);
+ DictionaryValue* item;
+ switch (type) {
+ case menus::MenuModel::TYPE_SEPARATOR:
+ item = CreateMenuItem(index, "separator", &has_icon);
+ break;
+ case menus::MenuModel::TYPE_RADIO:
+ has_icon = true; // all radio buttons has indicator icon.
+ item = CreateMenuItem(index, "radio", &has_icon);
+ break;
+ case menus::MenuModel::TYPE_SUBMENU:
+ item = CreateMenuItem(index, "submenu", &has_icon);
+ break;
+ case menus::MenuModel::TYPE_COMMAND:
+ item = CreateMenuItem(index, "command", &has_icon);
+ break;
+ case menus::MenuModel::TYPE_CHECK:
+ item = CreateMenuItem(index, "check", &has_icon);
+ break;
+ default:
+ // TODO(oshima): We don't support BUTTOM_ITEM for now.
+ // I haven't decided how to implement zoom/cut&paste
+ // stuff, but may do somethign similar to what linux_views
+ // does.
+ NOTREACHED();
+ continue;
+ }
+ items->Set(index, item);
+ }
+ model.SetBoolean("has_icon", has_icon);
+ model.SetBoolean("is_root", menu_widget_->is_root());
+
+ std::string json_model;
+ base::JSONWriter::Write(&model, false, &json_model);
+ std::wstring script = UTF8ToWide("updateModel(" + json_model + ")");
+ menu_widget_->ExecuteJavascript(script);
+}
+
+void NativeMenuDOMUI::UpdateStates() {
+ // Update menu contnets and submenus.
+ Rebuild();
+}
+
+gfx::NativeMenu NativeMenuDOMUI::GetNativeMenu() const {
+ NOTREACHED();
+ return NULL;
+}
+
+NativeMenuDOMUI::MenuAction NativeMenuDOMUI::GetMenuAction() const {
+ return menu_action_;
+}
+
+void NativeMenuDOMUI::AddMenuListener(views::MenuListener* listener) {
+ listeners_.AddObserver(listener);
+}
+
+void NativeMenuDOMUI::RemoveMenuListener(views::MenuListener* listener) {
+ listeners_.RemoveObserver(listener);
+}
+
+void NativeMenuDOMUI::SetMinimumWidth(int width) {
+ gtk_widget_set_size_request(menu_widget_->GetNativeView(), width, 1);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeMenuDOMUI, MessageLoopForUI::Dispatcher implementation:
+
+bool NativeMenuDOMUI::Dispatch(GdkEvent* event) {
+ switch (event->type) {
+ case GDK_MOTION_NOTIFY: {
+ NativeMenuDOMUI* target = FindMenuAt(
+ gfx::Point(event->motion.x_root, event->motion.y_root));
+ if (target)
+ target->menu_widget_->EnableInput(true);
+ break;
+ }
+ case GDK_BUTTON_PRESS: {
+ NativeMenuDOMUI* target = FindMenuAt(
+ gfx::Point(event->motion.x_root, event->motion.y_root));
+ if (!target) {
+ Hide();
+ return true;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ gtk_main_do_event(event);
+ return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeMenuDOMUI, MenuControl implementation:
+
+void NativeMenuDOMUI::Activate(menus::MenuModel* model, int index) {
+ NativeMenuDOMUI* root = GetRoot();
+ if (root) {
+ root->activated_menu_ = model;
+ root->activated_index_ = index;
+ root->menu_action_ = MENU_ACTION_SELECTED;
+ root->Hide();
+ }
+}
+
+void NativeMenuDOMUI::OpenSubmenu(int index, int y) {
+ submenu_.reset();
+ // Returns the model for the submenu at the specified index.
+ menus::MenuModel* submenu = model_->GetSubmenuModelAt(index);
+ submenu_.reset(new chromeos::NativeMenuDOMUI(submenu, false));
+ // y in menu_widget_ coordinate.
+ submenu_->set_parent(this);
+ submenu_->ShowAt(
+ MenuLocator::CreateSubMenuLocator(
+ menu_widget_,
+ menu_widget_->menu_locator()->GetSubmenuDirection(),
+ y));
+}
+
+void NativeMenuDOMUI::CloseAll() {
+ NativeMenuDOMUI* root = GetRoot();
+ // root can be null if the submenu is detached from parent.
+ if (root)
+ root->Hide();
+}
+
+void NativeMenuDOMUI::CloseSubmenu() {
+ submenu_.reset(); // This closes subsequent children.
+}
+
+void NativeMenuDOMUI::MoveInputToSubmenu() {
+ if (submenu_.get()) {
+ submenu_->menu_widget_->EnableInput(true);
+ }
+}
+
+void NativeMenuDOMUI::MoveInputToParent() {
+ if (parent_) {
+ parent_->menu_widget_->EnableInput(true);
+ }
+}
+
+void NativeMenuDOMUI::OnLoad() {
+ Rebuild();
+}
+
+void NativeMenuDOMUI::SetSize(const gfx::Size& size) {
+ menu_widget_->SetSize(size);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeMenuDOMUI, public:
+
+void NativeMenuDOMUI::Hide() {
+ // Only root can hide and exit the message loop.
+ DCHECK(menu_widget_->is_root());
+ DCHECK(!parent_);
+ if (!menu_shown_) {
+ // The menu has been already hidden by us and we're in the process of
+ // quiting the message loop..
+ return;
+ }
+ CloseSubmenu();
+ menu_shown_ = false;
+ MessageLoop::current()->Quit();
+}
+
+NativeMenuDOMUI* NativeMenuDOMUI::GetRoot() {
+ NativeMenuDOMUI* ancestor = this;
+ while (ancestor->parent_)
+ ancestor = ancestor->parent_;
+ if (ancestor->menu_widget_->is_root())
+ return ancestor;
+ else
+ return NULL;
+}
+
+Profile* NativeMenuDOMUI::GetProfile() {
+ Browser* browser = BrowserList::GetLastActive();
+ // browser can be null in login screen.
+ if (!browser)
+ return ProfileManager::GetDefaultProfile();
+ return browser->GetProfile();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// NativeMenuDOMUI, private:
+
+void NativeMenuDOMUI::ProcessActivate() {
+ if (activated_menu_ &&
+ activated_menu_->IsEnabledAt(activated_index_) &&
+ MenuTypeCanExecute(activated_menu_->GetTypeAt(activated_index_))) {
+ activated_menu_->ActivatedAt(activated_index_);
+ }
+}
+
+DictionaryValue* NativeMenuDOMUI::CreateMenuItem(
+ int index, const char* type, bool* has_icon_out) {
+ // Note: DOM UI uses '&' as mnemonic.
+ string16 label16 = model_->GetLabelAt(index);
+ DictionaryValue* item = new DictionaryValue();
+
+ item->SetString("type", type);
+ item->SetString("label", label16);
+ item->SetBoolean("enabled", model_->IsEnabledAt(index));
+ item->SetBoolean("visible", model_->IsVisibleAt(index));
+ item->SetBoolean("checked", model_->IsItemCheckedAt(index));
+ item->SetInteger("command_id", model_->GetCommandIdAt(index));
+ item->SetString(
+ "font", WideToUTF16(GetFontShorthand(model_->GetLabelFontAt(index))));
+ SkBitmap icon;
+ if (model_->GetIconAt(index, &icon) && !icon.isNull() && !icon.empty()) {
+ item->SetString("icon", dom_ui_util::GetImageDataUrl(icon));
+ *has_icon_out = true;
+ }
+ return item;
+}
+
+void NativeMenuDOMUI::ShowAt(MenuLocator* locator) {
+ model_->MenuWillShow();
+ menu_widget_->ShowAt(locator);
+}
+
+NativeMenuDOMUI* NativeMenuDOMUI::FindMenuAt(const gfx::Point& point) {
+ if (submenu_.get()) {
+ NativeMenuDOMUI* found = submenu_->FindMenuAt(point);
+ if (found) return found;
+ }
+ gfx::Rect bounds;
+ menu_widget_->GetBounds(&bounds, false);
+ return bounds.Contains(point) ? this : NULL;
+}
+
+} // namespace chromeos
+
+////////////////////////////////////////////////////////////////////////////////
+// MenuWrapper, public:
+
+namespace views {
+
+// static
+MenuWrapper* MenuWrapper::CreateWrapper(Menu2* menu) {
+ menus::MenuModel* model = menu->model();
+ return new chromeos::NativeMenuDOMUI(model, true);
+}
+
+} // namespace views
diff --git a/chrome/browser/chromeos/views/native_menu_domui.h b/chrome/browser/chromeos/views/native_menu_domui.h
new file mode 100644
index 0000000..5f6401d
--- /dev/null
+++ b/chrome/browser/chromeos/views/native_menu_domui.h
@@ -0,0 +1,130 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_CHROMEOS_VIEWS_NATIVE_MENU_DOMUI_H_
+#define CHROME_BROWSER_CHROMEOS_VIEWS_NATIVE_MENU_DOMUI_H_
+#pragma once
+
+#include <vector>
+
+#include "base/message_loop.h"
+#include "base/observer_list.h"
+#include "chrome/browser/chromeos/dom_ui/domui_menu_control.h"
+#include "views/controls/menu/menu_wrapper.h"
+
+class SkBitmap;
+class DictionaryValue;
+class Profile;
+
+namespace menus {
+class MenuModel;
+} // namespace menus
+
+namespace chromeos {
+
+class MenuLocator;
+class DOMUIMenuWidget;
+
+// A DOMUI implementation of MenuWrapper.
+class NativeMenuDOMUI : public views::MenuWrapper,
+ public DOMUIMenuControl,
+ public MessageLoopForUI::Dispatcher {
+ public:
+ NativeMenuDOMUI(menus::MenuModel* menu_model, bool root);
+ virtual ~NativeMenuDOMUI();
+
+ // Returns true if menu is currently shown.
+ bool is_menu_shown() { return menu_shown_; }
+
+ // Set parent menu.
+ void set_parent(NativeMenuDOMUI* parent) { parent_ = parent; }
+
+ // Overridden from MenuWrapper:
+ virtual void RunMenuAt(const gfx::Point& point, int alignment);
+ virtual void CancelMenu();
+ virtual void Rebuild();
+ virtual void UpdateStates();
+ virtual gfx::NativeMenu GetNativeMenu() const;
+ virtual MenuAction GetMenuAction() const;
+ virtual void AddMenuListener(views::MenuListener* listener);
+ virtual void RemoveMenuListener(views::MenuListener* listener);
+ virtual void SetMinimumWidth(int width);
+
+ // Overriden from MessageLoopForUI::Dispatcher:
+ virtual bool Dispatch(GdkEvent* event);
+
+ // Overriden from DOMUIMenuControl;
+ virtual menus::MenuModel* GetMenuModel() { return model_; }
+ virtual void Activate(menus::MenuModel* model, int index);
+ virtual void CloseAll();
+ virtual void CloseSubmenu();
+ virtual void MoveInputToParent();
+ virtual void MoveInputToSubmenu();
+ virtual void OnLoad();
+ virtual void OpenSubmenu(int index, int y);
+ virtual void SetSize(const gfx::Size& size);
+
+ // Hide All menu (including submenus).
+ void Hide();
+
+ // Returns the root of the menu tree. Returns NULL if it cannot find
+ // a root. (i.e. detached from root)
+ NativeMenuDOMUI* GetRoot();
+
+ // Returns the profile to create DOMView.
+ Profile* GetProfile();
+
+ private:
+ // Callback that we should really process the menu activation.
+ // See description above class for why we delay processing activation.
+ void ProcessActivate();
+
+ // Creates a menu item for the menu item at index.
+ DictionaryValue* CreateMenuItem(int index,
+ const char* type,
+ bool* has_icon_out);
+
+ // Show the menu using given |locator|.
+ void ShowAt(MenuLocator* locator);
+
+ // Find a menu object at point.
+ NativeMenuDOMUI* FindMenuAt(const gfx::Point& point);
+
+ // If we're a submenu, this is the parent.
+ NativeMenuDOMUI* parent_;
+
+ // Holds the current submenu.
+ scoped_ptr<NativeMenuDOMUI> submenu_;
+
+ menus::MenuModel* model_;
+
+ // A window widget that draws the content of the menu.
+ DOMUIMenuWidget* menu_widget_;
+
+ // True if the menu is currently shown.
+ // Used only in root.
+ bool menu_shown_;
+
+ // If the user selects something from the menu this is the menu they selected
+ // it from. When an item is selected menu_activated_ on the root ancestor is
+ // set to the menu the user selected and after the nested message loop exits
+ // Activate is invoked on this menu.
+ menus::MenuModel* activated_menu_;
+
+ // The index of the item the user selected. This is set on the
+ // actual menu the user selects and not the root.
+ int activated_index_;
+
+ // The action that took place during the call to RunMenuAt.
+ MenuAction menu_action_;
+
+ // Vector of listeners to receive callbacks when the menu opens.
+ ObserverList<views::MenuListener> listeners_;
+
+ DISALLOW_COPY_AND_ASSIGN(NativeMenuDOMUI);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CHROMEOS_VIEWS_NATIVE_MENU_DOMUI_H_
diff --git a/chrome/browser/dom_ui/dom_ui_factory.cc b/chrome/browser/dom_ui/dom_ui_factory.cc
index 4cc5272..1a70756 100644
--- a/chrome/browser/dom_ui/dom_ui_factory.cc
+++ b/chrome/browser/dom_ui/dom_ui_factory.cc
@@ -41,6 +41,7 @@
#include "chrome/browser/chromeos/dom_ui/mobile_setup_ui.h"
#include "chrome/browser/chromeos/dom_ui/register_page_ui.h"
#include "chrome/browser/chromeos/dom_ui/system_info_ui.h"
+#include "chrome/browser/chromeos/dom_ui/menu_ui.h"
#include "chrome/browser/dom_ui/filebrowse_ui.h"
#include "chrome/browser/dom_ui/mediaplayer_ui.h"
#endif
@@ -163,6 +164,8 @@ static DOMUIFactoryFunction GetDOMUIFactoryFunction(Profile* profile,
return &NewDOMUI<SlideshowUI>;
if (url.host() == chrome::kChromeUISystemInfoHost)
return &NewDOMUI<SystemInfoUI>;
+ if (url.host() == chrome::kChromeUIMenu)
+ return &NewDOMUI<chromeos::MenuUI>;
#else
if (url.host() == chrome::kChromeUISettingsHost) {
if (CommandLine::ForCurrentProcess()->HasSwitch(
diff --git a/chrome/browser/resources/menu.css b/chrome/browser/resources/menu.css
new file mode 100644
index 0000000..44e584f
--- /dev/null
+++ b/chrome/browser/resources/menu.css
@@ -0,0 +1,63 @@
+body {
+ margin: 0px;
+ background: -webkit-gradient(linear, left top, left bottom,
+ from(white),
+ to(#EEE));
+ margin: 0px 0px 0px 0px;
+}
+
+.menu_item {
+ margin-left: 0px;
+ margin-right: 0px;
+ margin-top: 0px;
+ white-space: nowrap;
+ padding: 0px 20px 0px 5px;
+}
+
+.disabled {
+ color: #b7b7b7;
+}
+
+.noicon {
+ padding-left: 20px;
+}
+
+.menu_label {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.left_icon {
+ vertical-align: middle;
+ margin-right: 10px;
+}
+
+.right_icon {
+ vertical-align: middle;
+ right: 10px;
+}
+
+#menu {
+ width: 100%;
+ height: 100%;
+}
+
+.separator {
+ width: 85%;
+ height: 1px;
+ margin-top: 3px;
+ margin-bottom: 3px;
+ background: -webkit-gradient(linear, left top, right top,
+ from(transparent),
+ color-stop(0.3, rgba(20, 20, 20, 0.5)),
+ color-stop(0.7, rgba(20, 20, 20, 0.5)),
+ to(transparent));
+}
+
+.mnemonic_enabled .mnemonic {
+ text-decoration: underline;
+}
+
+.selected {
+ background: #DCE4FA;
+}
diff --git a/chrome/browser/resources/menu.html b/chrome/browser/resources/menu.html
new file mode 100644
index 0000000..4cea45c
--- /dev/null
+++ b/chrome/browser/resources/menu.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" href="menu.css"/>
+ <script src="shared/js/class_list.js"></script>
+ <script src="menu.js"></script>
+ </head>
+ <body>
+ <div id="menu"> </div>
+ <script>
+ setMenu(new Menu());
+ </script>
+ </body>
+</html>
+
diff --git a/chrome/browser/resources/menu.js b/chrome/browser/resources/menu.js
new file mode 100644
index 0000000..f843e656
--- /dev/null
+++ b/chrome/browser/resources/menu.js
@@ -0,0 +1,474 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// How long to wait to open submenu when mouse hovers.
+var SUBMENU_OPEN_DELAY_MS = 200;
+// How long to wait to close submenu when mouse left.
+var SUBMENU_CLOSE_DELAY_MS = 500;
+// Regular expression to match/find mnemonic key.
+var MNEMONIC_REGEXP = /&(.)/;
+
+/**
+ * Sends 'click' DOMUI message.
+ */
+function sendClick(id) {
+ chrome.send('click', [ id + '' ]);
+}
+
+/**
+ * MenuItem class.
+ */
+function MenuItem(menu, id, attrs) {
+ this.menu_ = menu;
+ this.id = id;
+ this.attrs = attrs;
+}
+
+MenuItem.prototype = {
+ /**
+ * Initialize the MenuItem.
+ * @param {boolean} has_icon True if the menu has left icon.
+ * @public
+ */
+ init: function(has_icon) {
+ this.div = document.createElement('div');
+ var attrs = this.attrs;
+ if (attrs.type == 'separator') {
+ this.div.className = 'separator';
+ } else if (attrs.type == 'command' ||
+ attrs.type == 'submenu' ||
+ attrs.type == 'check' ||
+ attrs.type == 'radio') {
+ this.initMenuItem(has_icon);
+ } else {
+ this.div.className = 'menu_item disabled';
+ this.div.innerHTML = 'unknown';
+ }
+ this.div.classList.add(has_icon ? 'has_icon' : 'noicon');
+ },
+
+ /**
+ * Select this item.
+ * @public
+ */
+ select: function() {
+ this.div.classList.add('selected');
+ this.menu_.setSelection(this);
+ },
+
+ /**
+ * Unselect this item.
+ * @public
+ */
+ unselect: function() {
+ this.div.classList.remove('selected');
+ },
+
+ /**
+ * Activat the menu item.
+ * @public
+ */
+ activate: function() {
+ if (this.attrs.type == 'submenu') {
+ this.menu_.openSubmenu(this);
+ } else if (this.attrs.type != 'separator') {
+ sendClick(this.id);
+ }
+ },
+
+ /**
+ * Sends open_submenu DOMUI message.
+ * @public
+ */
+ sendOpenSubmenuCommand: function() {
+ chrome.send('open_submenu', [ this.id + '', this.getYCoord_() + '']);
+ },
+
+ /**
+ * Returns y coordinate of this menu item
+ * in the menu window.
+ * @private
+ */
+ getYCoord_: function() {
+ var element = this.div;
+ var y = 0;
+ while (element != null) {
+ y += element.offsetTop;
+ element = element.offsetParent;
+ }
+ return y;
+ },
+
+ /**
+ * Internal method to initiailze the MenuItem's div element.
+ * @private
+ */
+ initMenuItem: function(has_icon) {
+ var attrs = this.attrs;
+ this.div.className = 'menu_item ' + attrs.type;
+ this.menu_.addHandlers(this);
+ var mnemonic = MNEMONIC_REGEXP.exec(attrs.label);
+ if (mnemonic) {
+ var c = mnemonic[1];
+ this.menu_.registerMnemonicKey(c, this);
+ }
+ var text = attrs.label.replace(
+ MNEMONIC_REGEXP, '<span class="mnemonic">$1</span>');
+ if (has_icon) {
+ var icon = document.createElement('image');
+ icon.className = 'left_icon';
+
+ if (attrs.type == 'radio') {
+ icon.src = attrs.checked ?
+ menu_.config_.radioOnUrl : menu_.config_.radioOffUrl;
+ } else if (attrs.icon) {
+ icon.src = attrs.icon;
+ } else if (attrs.type == 'check' && attrs.checked) {
+ icon.src = menu_.config_.checkUrl;
+ } else {
+ icon.style.width = '12px';
+ }
+ this.div.appendChild(icon);
+ }
+ var label = document.createElement('div');
+ label.className = 'menu_label';
+ label.innerHTML = text;
+
+ if (attrs.font) {
+ label.style.font = attrs.font;
+ }
+ this.div.appendChild(label);
+
+ if (attrs.type == 'submenu') {
+ var icon = document.createElement('image');
+ icon.src = menu_.config_.arrowUrl;
+ icon.className = 'right_icon';
+ this.div.appendChild(icon);
+ }
+ },
+};
+
+/**
+ * Menu class.
+ */
+function Menu() {
+ /* configuration object */
+ this.config_ = null;
+ /* currently selected menu item */
+ this.current_ = null;
+ /* the id of last element */
+ this.last_id_ = -1;
+ /* timers for opening/closing submenu */
+ this.open_submenu_timer_ = 0;
+ this.close_submenu_timer_ = 0;
+ /* pointer to a submenu currently shown, if any */
+ this.submenu_shown_ = null;
+ /* list of menu items */
+ this.items_ = [];
+ /* map from mnemonic character to item to activate */
+ this.mnemonics_ = {};
+ /* true if this menu is root */
+ this.is_root_ = false;
+}
+
+Menu.prototype = {
+ /**
+ * Initialize the menu.
+ * @public
+ */
+ init: function(config) {
+ this.config_ = config;
+ document.getElementById('menu').onmouseout = this.onMouseout_.bind(this);
+ document.addEventListener('keydown', this.onKeydown_.bind(this));
+ /* disable text select */
+ document.onselectstart = function() { return false; }
+ },
+
+ /**
+ * A template method to create MenuItem object.
+ * Subclass class can override to return custom menu item.
+ * @public
+ */
+ createMenuItem: function(id, attrs) {
+ return new MenuItem(this, id, attrs);
+ },
+
+ /**
+ * Update and display the new model.
+ * @public
+ */
+ updateModel: function(model) {
+ this.is_root = model.is_root;
+ this.current_ = null;
+ this.items_ = [];
+ this.mnemonics_ = {};
+
+ var menu = document.getElementById('menu');
+ menu.innerHTML = ''; // remove menu items
+
+ var id = 0;
+
+ for (i in model.items) {
+ var attrs = model.items[i];
+ var item = this.createMenuItem(id++, attrs);
+ this.items_[item.id] = item;
+ this.last_id_ = item.id;
+ if (!item.attrs.visible) {
+ continue;
+ }
+
+ item.init(model.has_icon);
+ menu.appendChild(item.div);
+ }
+ },
+
+ /**
+ * Highlights the currently selected item, or
+ * select the 1st selectable item if none is selected.
+ * @public
+ */
+ showSelection: function() {
+ if (this.current_) {
+ this.current_.select();
+ } else {
+ this.findNextEnabled_(1).select();
+ }
+ },
+
+ /**
+ * Registers mnemonic key.
+ * @param {c} a mnemonic key to activate item.
+ * @param {item} an item to be activated when {c} is pressed.
+ * @public
+ */
+ registerMnemonicKey: function(c, item) {
+ this.mnemonics_[c.toUpperCase()] = item;
+ this.mnemonics_[c.toLowerCase()] = item;
+ },
+
+ /**
+ * Add event handlers for the item.
+ * @public
+ */
+ addHandlers: function(item) {
+ var menu = this;
+ item.div.addEventListener('mouseover', function(event) {
+ menu.onMouseover_(event, item);
+ });
+ if (item.attrs.enabled) {
+ item.div.addEventListener('mouseup', function(event) {
+ menu.onClick_(event, item);
+ });
+ } else {
+ item.div.classList.add('disabled');
+ }
+ },
+
+ /**
+ * Set the selected item. This also start or cancel
+ * @public
+ */
+ setSelection: function(item) {
+ if (this.current_ == item)
+ return;
+
+ if (this.current_ != null)
+ this.current_.unselect();
+
+ this.current_ = item;
+
+ var menu = this;
+ if (item.attrs.type == 'submenu') {
+ if (this.submenu_shown_ != item) {
+ this.open_submenu_timer_ =
+ setTimeout(function() { menu.openSubmenu(item); },
+ SUBMENU_OPEN_DELAY_MS);
+ } else {
+ this.cancelSubmenuTimer_();
+ }
+ } else if (this.submenu_shown_) {
+ this.cancelSubmenuTimer_();
+ this.close_submenu_timer_ =
+ setTimeout(function() { menu.closeSubmenu_(item); },
+ SUBMENU_CLOSE_DELAY_MS);
+ }
+ },
+
+ /**
+ * Open submenu {item}. It does nothing if the submenu is
+ * already opened.
+ * @param {item} the submenu item to open.
+ * @public
+ */
+ openSubmenu: function(item) {
+ this.cancelSubmenuTimer_();
+ if (this.submenu_shown_ != item) {
+ this.submenu_shown_ = item;
+ item.sendOpenSubmenuCommand();
+ }
+ },
+
+ /**
+ * Handle keyboard navigatio and mnemonic keys.
+ * @private
+ */
+ onKeydown_: function(event) {
+ switch (event.keyCode) {
+ case 27: /* escape */
+ sendClick(-1); // -1 closes the menu.
+ break;
+ case 37: /* left */
+ this.moveToParent_();
+ break;
+ case 39: /* right */
+ this.moveToSubmenu_();
+ break;
+ case 38: /* up */
+ document.getElementById('menu').className = 'mnemonic_enabled';
+ this.findNextEnabled_(-1).select();
+ break;
+ case 40: /* down */
+ document.getElementById('menu').className = 'mnemonic_enabled';
+ this.findNextEnabled_(1).select();
+ break;
+ case 9: /* tab */
+ // TBD.
+ break;
+ case 13: /* return */
+ case 32: /* space */
+ if (this.current_) {
+ this.current_.activate();
+ }
+ break;
+ default:
+ // Handles mnemonic.
+ var c = String.fromCharCode(event.keyCode);
+ var item = this.mnemonics_[c];
+ if (item) item.activate();
+ }
+ },
+
+ // Mouse Event handlers
+ onClick_: function(event, item) {
+ item.activate();
+ },
+
+ onMouseover_: function(event, item) {
+ this.cancelSubmenuTimer_();
+ if (this.current_ != item && item.attrs.enabled) {
+ item.select();
+ }
+ },
+
+ onMouseout_: function(event) {
+ if (this.current_) {
+ this.current_.unselect();
+ this.current_ = null;
+ }
+ },
+
+ /**
+ * Closes the submenu.
+ * a submenu.
+ * @private
+ */
+ closeSubmenu_: function(item) {
+ this.submenu_shown_ = null;
+ this.cancelSubmenuTimer_();
+ chrome.send('close_submenu', []);
+ },
+
+ /**
+ * Move the selection to parent menu if the current menu is
+ * a submenu.
+ * @private
+ */
+ moveToParent_: function() {
+ if (!this.is_root) {
+ if (this.current_) {
+ this.current_.unselect();
+ this.current_ = null;
+ }
+ chrome.send('move_to_parent', []);
+ }
+ },
+
+ /**
+ * Move the selection to submenu if the currently selected
+ * menu is a submenu.
+ * @private
+ */
+ moveToSubmenu_: function () {
+ var current = this.current_;
+ if(current && current.attrs.type == 'submenu') {
+ this.openSubmenu(current);
+ chrome.send('move_to_submenu', []);
+ }
+ },
+
+ /**
+ * Find a next selectable item. If nothing is selected, the 1st
+ * selectable item will be chosen. Returns null if nothing is
+ * selectable.
+ * @param {number} incr specifies the direction to search, 1 to
+ * downwards and -1 for upwards.
+ * @private
+ */
+ findNextEnabled_: function(incr) {
+ if (this.current_) {
+ var id = parseInt(this.current_.id);
+ } else {
+ var id = incr > 0 ? -1 : this.last_id_ + 1;
+ }
+ for (var i = 0; i <= this.last_id_; i++) {
+ if (id == 0 && incr < 0) {
+ id = this.last_id_;
+ } else if (id == this.last_id_ && incr > 0) {
+ id = 0;
+ } else {
+ id += incr;
+ }
+ var item = this.items_[id];
+ if (item.attrs.enabled && item.attrs.type != 'separator')
+ return item;
+ }
+ return null;
+ },
+
+ /**
+ * Cancels timers to open/close submenus.
+ * @private
+ */
+ cancelSubmenuTimer_: function() {
+ if (this.open_submenu_timer_) {
+ clearTimeout(this.open_submenu_timer_);
+ this.open_submenu_timer_ = 0;
+ }
+ if (this.close_submenu_timer_) {
+ clearTimeout(this.close_submenu_timer_);
+ this.close_submenu_timer_ = 0;
+ }
+ },
+};
+
+/**
+ * functions to be called from C++.
+ */
+function init(config) {
+ menu_.init(config);
+}
+
+function selectItem() {
+ menu_.showSelection();
+}
+
+function updateModel(model) {
+ menu_.updateModel(model);
+}
+
+var menu_;
+
+function setMenu(menu) {
+ menu_ = menu;
+}
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 7074c8f..da8472e 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -460,6 +460,9 @@
'browser/chromeos/dom_ui/proxy_handler.h',
'browser/chromeos/dom_ui/register_page_ui.cc',
'browser/chromeos/dom_ui/register_page_ui.h',
+ 'browser/chromeos/dom_ui/domui_menu_control.h',
+ 'browser/chromeos/dom_ui/menu_ui.cc',
+ 'browser/chromeos/dom_ui/menu_ui.h',
'browser/chromeos/dom_ui/system_info_ui.cc',
'browser/chromeos/dom_ui/system_info_ui.h',
'browser/chromeos/dom_ui/system_options_handler.cc',
@@ -704,6 +707,12 @@
'browser/chromeos/version_loader.cc',
'browser/chromeos/version_loader.h',
'browser/chromeos/view_ids.h',
+ 'browser/chromeos/views/menu_locator.cc',
+ 'browser/chromeos/views/menu_locator.h',
+ 'browser/chromeos/views/native_menu_domui.cc',
+ 'browser/chromeos/views/native_menu_domui.h',
+ 'browser/chromeos/views/domui_menu_widget.cc',
+ 'browser/chromeos/views/domui_menu_widget.h',
'browser/chromeos/volume_bubble.cc',
'browser/chromeos/volume_bubble.h',
'browser/chromeos/volume_bubble_view.cc',
diff --git a/chrome/common/url_constants.cc b/chrome/common/url_constants.cc
index 7e880d3..d92f236 100644
--- a/chrome/common/url_constants.cc
+++ b/chrome/common/url_constants.cc
@@ -119,6 +119,7 @@ const char kChromeUISettingsHost[] = "settings";
const char kChromeUISyncResourcesHost[] = "syncresources";
const char kChromeUIThemePath[] = "theme";
const char kChromeUIThumbnailPath[] = "thumb";
+const char kChromeUIMenu[] = "menu";
#if defined(OS_CHROMEOS)
const char kChromeUIFileBrowseHost[] = "filebrowse";
diff --git a/chrome/common/url_constants.h b/chrome/common/url_constants.h
index ecde9ee..b7d56b9 100644
--- a/chrome/common/url_constants.h
+++ b/chrome/common/url_constants.h
@@ -113,6 +113,7 @@ extern const char kChromeUISettingsHost[];
extern const char kChromeUISyncResourcesHost[];
extern const char kChromeUIThemePath[];
extern const char kChromeUIThumbnailPath[];
+extern const char kChromeUIMenu[];
#if defined(OS_CHROMEOS)
extern const char kChromeUIFileBrowseHost[];
diff --git a/views/controls/menu/native_menu_gtk.cc b/views/controls/menu/native_menu_gtk.cc
index b65d7e5..14c1f6d 100644
--- a/views/controls/menu/native_menu_gtk.cc
+++ b/views/controls/menu/native_menu_gtk.cc
@@ -498,9 +498,13 @@ void NativeMenuGtk::MenuDestroyed(GtkWidget* widget, Menu2* menu2) {
////////////////////////////////////////////////////////////////////////////////
// MenuWrapper, public:
+#if !defined(OS_CHROMEOS)
+
// static
MenuWrapper* MenuWrapper::CreateWrapper(Menu2* menu) {
return new NativeMenuGtk(menu);
}
+#endif // OS_CHROMEOS
+
} // namespace views