diff options
author | oshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-04 23:59:23 +0000 |
---|---|---|
committer | oshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-04 23:59:23 +0000 |
commit | 3a051a4b8a96fb3edd00ebddcd5eb48e8ecf4396 (patch) | |
tree | 7728a74b027dced0037b18f142329c10954bb7a0 | |
parent | 23714db12751e6ba10a33a5110359af139d5566c (diff) | |
download | chromium_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.grd | 1 | ||||
-rw-r--r-- | chrome/browser/chromeos/dom_ui/domui_menu_control.h | 60 | ||||
-rw-r--r-- | chrome/browser/chromeos/dom_ui/menu_ui.cc | 345 | ||||
-rw-r--r-- | chrome/browser/chromeos/dom_ui/menu_ui.h | 58 | ||||
-rw-r--r-- | chrome/browser/chromeos/views/domui_menu_widget.cc | 294 | ||||
-rw-r--r-- | chrome/browser/chromeos/views/domui_menu_widget.h | 104 | ||||
-rw-r--r-- | chrome/browser/chromeos/views/menu_locator.cc | 285 | ||||
-rw-r--r-- | chrome/browser/chromeos/views/menu_locator.h | 79 | ||||
-rw-r--r-- | chrome/browser/chromeos/views/native_menu_domui.cc | 407 | ||||
-rw-r--r-- | chrome/browser/chromeos/views/native_menu_domui.h | 130 | ||||
-rw-r--r-- | chrome/browser/dom_ui/dom_ui_factory.cc | 3 | ||||
-rw-r--r-- | chrome/browser/resources/menu.css | 63 | ||||
-rw-r--r-- | chrome/browser/resources/menu.html | 16 | ||||
-rw-r--r-- | chrome/browser/resources/menu.js | 474 | ||||
-rw-r--r-- | chrome/chrome_browser.gypi | 9 | ||||
-rw-r--r-- | chrome/common/url_constants.cc | 1 | ||||
-rw-r--r-- | chrome/common/url_constants.h | 1 | ||||
-rw-r--r-- | views/controls/menu/native_menu_gtk.cc | 4 |
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 |