diff options
author | oshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-12 04:21:08 +0000 |
---|---|---|
committer | oshima@chromium.org <oshima@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-10-12 04:21:08 +0000 |
commit | 090bc22fd9703492c5cf2f76a0a9c16ffa580e0a (patch) | |
tree | 8ee44ee5a82ba4007b9403c04901e353586ec198 /chrome | |
parent | 3f96c4e4b7254361caccc433be724469438cbc95 (diff) | |
download | chromium_src-090bc22fd9703492c5cf2f76a0a9c16ffa580e0a.zip chromium_src-090bc22fd9703492c5cf2f76a0a9c16ffa580e0a.tar.gz chromium_src-090bc22fd9703492c5cf2f76a0a9c16ffa580e0a.tar.bz2 |
Switch WrenchMenu to use domui.
* Add WrenchMenu class that implements zoom/edit commands.
* changed toolkit_view to use domui menu.
* Add accelerator support.
* allow subclass to have its own css.
this also addresses comments on
* removed clearTimeout
* move init code to decorate as much as possible
* changed init:config to setter
* use 'hidden' rule to hide it, instead of not adding it.
* I didn't change addHandler as now target and menuitem does not necessary the same.
BUG=6497
TEST=
Review URL: http://codereview.chromium.org/3601021
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@62244 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/browser/browser_resources.grd | 1 | ||||
-rw-r--r-- | chrome/browser/chromeos/dom_ui/menu_ui.cc | 74 | ||||
-rw-r--r-- | chrome/browser/chromeos/dom_ui/menu_ui.h | 8 | ||||
-rw-r--r-- | chrome/browser/chromeos/dom_ui/wrench_menu_ui.cc | 63 | ||||
-rw-r--r-- | chrome/browser/chromeos/dom_ui/wrench_menu_ui.h | 20 | ||||
-rw-r--r-- | chrome/browser/resources/menu.css | 14 | ||||
-rw-r--r-- | chrome/browser/resources/menu.html | 1 | ||||
-rw-r--r-- | chrome/browser/resources/menu.js | 218 | ||||
-rw-r--r-- | chrome/browser/resources/wrench_menu.css | 43 | ||||
-rw-r--r-- | chrome/browser/resources/wrench_menu.js | 308 | ||||
-rw-r--r-- | chrome/browser/views/toolbar_view.cc | 21 | ||||
-rw-r--r-- | chrome/browser/views/toolbar_view.h | 10 |
12 files changed, 657 insertions, 124 deletions
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd index f981d4f..cecfec6 100644 --- a/chrome/browser/browser_resources.grd +++ b/chrome/browser/browser_resources.grd @@ -76,6 +76,7 @@ without changes to the corresponding grd file. eter --> <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" /> <include name="IDR_WRENCH_MENU_JS" file="resources\wrench_menu.js" flattenhtml="true" type="BINDATA" /> + <include name="IDR_WRENCH_MENU_CSS" file="resources\wrench_menu.css" flattenhtml="true" type="BINDATA" /> </if> </includes> </release> diff --git a/chrome/browser/chromeos/dom_ui/menu_ui.cc b/chrome/browser/chromeos/dom_ui/menu_ui.cc index 85264fa..19989b0 100644 --- a/chrome/browser/chromeos/dom_ui/menu_ui.cc +++ b/chrome/browser/chromeos/dom_ui/menu_ui.cc @@ -32,6 +32,7 @@ #include "gfx/font.h" #include "grit/app_resources.h" #include "grit/browser_resources.h" +#include "views/accelerator.h" #include "views/controls/menu/menu_config.h" #include "views/controls/menu/radio_button_image_gtk.h" #include "views/widget/widget_gtk.h" @@ -39,7 +40,7 @@ namespace { // a fake resource id for not loading extra resource. -const int kNoExtraSource = -1; +const int kNoExtraResource = -1; // A utility function that generates css font property from gfx::Font. std::wstring GetFontShorthand(const gfx::Font* font) { @@ -119,12 +120,16 @@ const std::string& GetImageDataUrlForRadio(bool on) { * Generates a html file that uses |menu_class| as a menu implementation. * |menu_source_id| specifies the source that contains the definition of the * |menu_class|, or empty string to use plain "Menu". + * + * TODO(oshima): make this template to avoid repeatedly loading the + * same source/css files. */ std::string GetMenuUIHTMLSourceFromString( const chromeos::MenuUI& menu_ui, const base::StringPiece& menu_template, const std::string& menu_class, - int menu_source_id) { + int menu_source_id, + int menu_css_id) { #define SET_INTEGER_PROPERTY(prop) \ value_config.SetInteger(#prop, menu_config.prop) @@ -177,13 +182,20 @@ std::string GetMenuUIHTMLSourceFromString( menu_class + ".decorate(document.getElementById('viewport'));" + " init(" + json_config + ");"); - if (menu_source_id == kNoExtraSource) { + if (menu_source_id == kNoExtraResource) { strings.SetString("menu_source", ""); } else { base::StringPiece menu_source( ResourceBundle::GetSharedInstance().GetRawDataResource(menu_source_id)); strings.SetString("menu_source", menu_source.as_string()); } + if (menu_css_id == kNoExtraResource) { + strings.SetString("menu_css", ""); + } else { + base::StringPiece menu_css( + ResourceBundle::GetSharedInstance().GetRawDataResource(menu_css_id)); + strings.SetString("menu_css", menu_css.as_string()); + } return jstemplate_builder::GetI18nTemplateHtml(menu_template, &strings); } @@ -194,7 +206,8 @@ class MenuUIHTMLSource : public ChromeURLDataManager::DataSource, MenuUIHTMLSource(const chromeos::MenuUI& menu_ui, const std::string& source_name, const std::string& menu_class, - int menu_source_id); + int menu_source_id, + int menu_css_id); // Called when the network layer has requested a resource underneath // the path we registered. @@ -222,8 +235,12 @@ class MenuUIHTMLSource : public ChromeURLDataManager::DataSource, // The name of JS Menu class to use. const std::string menu_class_; - // The resource id of the file of the menu subclass. + // The resource id of the JS file of the menu subclass. int menu_source_id_; + + // The resource id of the CSS file of the menu subclass. + int menu_css_id_; + #ifndef NDEBUG int request_id_; #endif @@ -301,11 +318,13 @@ class MenuHandler : public chromeos::MenuHandlerBase, MenuUIHTMLSource::MenuUIHTMLSource(const chromeos::MenuUI& menu_ui, const std::string& source_name, const std::string& menu_class, - int menu_source_id) + int menu_source_id, + int menu_css_id) : DataSource(source_name, MessageLoop::current()), menu_ui_(menu_ui), menu_class_(menu_class), - menu_source_id_(menu_source_id) + menu_source_id_(menu_source_id), + menu_css_id_(menu_css_id) #ifndef NDEBUG , request_id_(-1) #endif @@ -334,7 +353,7 @@ void MenuUIHTMLSource::StartDataRequest(const std::string& path, // The resource string should be pure code and should not contain // i18n string. const std::string menu_html = GetMenuUIHTMLSourceFromString( - menu_ui_, menu_template, menu_class_, menu_source_id_); + menu_ui_, menu_template, menu_class_, menu_source_id_, menu_css_id_); scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes); @@ -354,7 +373,7 @@ void MenuUIHTMLSource::OnURLFetchComplete(const URLFetcher* source, #ifndef NDEBUG // This should not be called in release build. const std::string menu_html = GetMenuUIHTMLSourceFromString( - menu_ui_, data, menu_class_, menu_source_id_); + menu_ui_, data, menu_class_, menu_source_id_, menu_css_id_); scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes); @@ -588,27 +607,33 @@ void MenuUI::ModelUpdated(const menus::MenuModel* model) { ListValue* items = new ListValue(); json_model.Set("items", items); int max_icon_width = 0; + bool has_accelerator = 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(model, index, "separator", &max_icon_width); + item = CreateMenuItem(model, index, "separator", + &max_icon_width, &has_accelerator); break; case menus::MenuModel::TYPE_RADIO: max_icon_width = std::max(max_icon_width, 12); - item = CreateMenuItem(model, index, "radio", &max_icon_width); + item = CreateMenuItem(model, index, "radio", + &max_icon_width, &has_accelerator); break; case menus::MenuModel::TYPE_SUBMENU: - item = CreateMenuItem(model, index, "submenu", &max_icon_width); + item = CreateMenuItem(model, index, "submenu", + &max_icon_width, &has_accelerator); break; case menus::MenuModel::TYPE_COMMAND: - item = CreateMenuItem(model, index, "command", &max_icon_width); + item = CreateMenuItem(model, index, "command", + &max_icon_width, &has_accelerator); break; case menus::MenuModel::TYPE_CHECK: // Add space even when unchecked. max_icon_width = std::max(max_icon_width, 12); - item = CreateMenuItem(model, index, "check", &max_icon_width); + item = CreateMenuItem(model, index, "check", + &max_icon_width, &has_accelerator); break; default: // TODO(oshima): We don't support BUTTOM_ITEM for now. @@ -626,13 +651,15 @@ void MenuUI::ModelUpdated(const menus::MenuModel* model) { DCHECK(widget); json_model.SetInteger("maxIconWidth", max_icon_width); json_model.SetBoolean("isRoot", widget->is_root()); + json_model.SetBoolean("hasAccelerator", has_accelerator); CallJavascriptFunction(L"updateModel", json_model); } DictionaryValue* MenuUI::CreateMenuItem(const menus::MenuModel* model, int index, const char* type, - int* max_icon_width) const { + int* max_icon_width, + bool* has_accel) const { // Note: DOM UI uses '&' as mnemonic. string16 label16 = model->GetLabelAt(index); DictionaryValue* item = new DictionaryValue(); @@ -650,6 +677,11 @@ DictionaryValue* MenuUI::CreateMenuItem(const menus::MenuModel* model, item->SetString("icon", dom_ui_util::GetImageDataUrl(icon)); *max_icon_width = std::max(*max_icon_width, icon.width()); } + views::Accelerator menu_accelerator; + if (model->GetAcceleratorAt(index, &menu_accelerator)) { + item->SetString("accel", WideToUTF16(menu_accelerator.GetShortcutText())); + *has_accel = true; + } return item; } @@ -657,15 +689,21 @@ ChromeURLDataManager::DataSource* MenuUI::CreateDataSource() { return CreateMenuUIHTMLSource(*this, chrome::kChromeUIMenu, "Menu" /* class name */, - kNoExtraSource); + kNoExtraResource, + kNoExtraResource); } ChromeURLDataManager::DataSource* MenuUI::CreateMenuUIHTMLSource( const MenuUI& menu_ui, const std::string& source_name, const std::string& menu_class, - int menu_source_id) { - return new MenuUIHTMLSource(menu_ui, source_name, menu_class, menu_source_id); + int menu_source_id, + int menu_css_id) { + return new MenuUIHTMLSource(menu_ui, + source_name, + menu_class, + menu_source_id, + menu_css_id); } } // namespace chromeos diff --git a/chrome/browser/chromeos/dom_ui/menu_ui.h b/chrome/browser/chromeos/dom_ui/menu_ui.h index 66cdf7f..6127d64 100644 --- a/chrome/browser/chromeos/dom_ui/menu_ui.h +++ b/chrome/browser/chromeos/dom_ui/menu_ui.h @@ -33,13 +33,14 @@ class MenuUI : public DOMUI { virtual DictionaryValue* CreateMenuItem(const menus::MenuModel* model, int index, const char* type, - int* max_icon_width) const; + int* max_icon_width, + bool* has_accel) const; // Subclass can add extra parameters or replaces default configuration. virtual void AddCustomConfigValues(DictionaryValue* config) const {}; // A utility function which creates a concrete html file from - // template file |menu_resource_id| for given |menu_class|. + // template file |menu_resource_id| and |menu_css_id| for given |menu_class|. // The resource_name is the host part of DOMUI's url. // Caution: This calls MenuUI::GetProfile() when creating the data source, // thus, it has to be initialized. @@ -47,7 +48,8 @@ class MenuUI : public DOMUI { const MenuUI& menu_ui, const std::string& source_name, const std::string& menu_class, - int menu_source_res_id); + int menu_source_res_id, + int menu_css_res_id); protected: // A constructor for subclass to initialize the MenuUI with diff --git a/chrome/browser/chromeos/dom_ui/wrench_menu_ui.cc b/chrome/browser/chromeos/dom_ui/wrench_menu_ui.cc index c79316b5..21a2913 100644 --- a/chrome/browser/chromeos/dom_ui/wrench_menu_ui.cc +++ b/chrome/browser/chromeos/dom_ui/wrench_menu_ui.cc @@ -4,13 +4,23 @@ #include "chrome/browser/chromeos/dom_ui/wrench_menu_ui.h" -#include "base/values.h" +#include "app/l10n_util.h" +#include "base/string_number_conversions.h" #include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "base/weak_ptr.h" #include "chrome/app/chrome_dll_resource.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_list.h" #include "chrome/browser/chromeos/views/native_menu_domui.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_source.h" #include "chrome/common/url_constants.h" #include "googleurl/src/gurl.h" #include "grit/browser_resources.h" +#include "grit/generated_resources.h" #include "views/controls/menu/menu_2.h" namespace chromeos { @@ -27,14 +37,59 @@ WrenchMenuUI::WrenchMenuUI(TabContents* contents) ALLOW_THIS_IN_INITIALIZER_LIST( CreateMenuUIHTMLSource(*this, chrome::kChromeUIWrenchMenu, - "Menu", - IDR_WRENCH_MENU_JS))) { + "WrenchMenu" /* class name */, + IDR_WRENCH_MENU_JS, + IDR_WRENCH_MENU_CSS))) { + registrar_.Add(this, NotificationType::ZOOM_LEVEL_CHANGED, + Source<Profile>(GetProfile())); +} + +void WrenchMenuUI::ModelUpdated(const menus::MenuModel* new_model) { + MenuUI::ModelUpdated(new_model); + UpdateZoomControls(); } void WrenchMenuUI::AddCustomConfigValues(DictionaryValue* config) const { - // These command ids are to create customized menu items for wrench menu. + // Resources that are necessary to build wrench menu. config->SetInteger("IDC_CUT", IDC_CUT); + config->SetInteger("IDC_COPY", IDC_COPY); + config->SetInteger("IDC_PASTE", IDC_PASTE); config->SetInteger("IDC_ZOOM_MINUS", IDC_ZOOM_MINUS); + config->SetInteger("IDC_ZOOM_PLUS", IDC_ZOOM_PLUS); + config->SetInteger("IDC_FULLSCREEN", IDC_FULLSCREEN); + + config->SetString("IDS_EDIT2", WideToUTF8(l10n_util::GetString(IDS_EDIT2))); + config->SetString("IDS_ZOOM_MENU2", + WideToUTF8(l10n_util::GetString(IDS_ZOOM_MENU2))); + config->SetString("IDS_CUT", WideToUTF8(l10n_util::GetString(IDS_CUT))); + config->SetString("IDS_COPY", WideToUTF8(l10n_util::GetString(IDS_COPY))); + config->SetString("IDS_PASTE", WideToUTF8(l10n_util::GetString(IDS_PASTE))); +} + +void WrenchMenuUI::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK_EQ(NotificationType::ZOOM_LEVEL_CHANGED, type.value); + UpdateZoomControls(); +} + +void WrenchMenuUI::UpdateZoomControls() { + Browser* browser = BrowserList::GetLastActive(); + if (!browser) + return; + TabContents* selected_tab = browser->GetSelectedTabContents(); + bool enable_increment = false; + bool enable_decrement = false; + int zoom = 100; + if (selected_tab) + zoom = selected_tab->GetZoomPercent(&enable_increment, &enable_decrement); + + DictionaryValue params; + params.SetBoolean("plus", enable_increment); + params.SetBoolean("minus", enable_decrement); + params.SetString("percent", l10n_util::GetStringFUTF16( + IDS_ZOOM_PERCENT, UTF8ToUTF16(base::IntToString(zoom)))); + CallJavascriptFunction(L"updateZoomControls", params); } views::Menu2* WrenchMenuUI::CreateMenu2(menus::MenuModel* model) { diff --git a/chrome/browser/chromeos/dom_ui/wrench_menu_ui.h b/chrome/browser/chromeos/dom_ui/wrench_menu_ui.h index 04fd44e..cb0cd80 100644 --- a/chrome/browser/chromeos/dom_ui/wrench_menu_ui.h +++ b/chrome/browser/chromeos/dom_ui/wrench_menu_ui.h @@ -7,6 +7,12 @@ #pragma once #include "chrome/browser/chromeos/dom_ui/menu_ui.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_type.h" + +class NotificationSource; +class NotificationDetails; namespace views { class Menu2; @@ -18,17 +24,29 @@ class MenuModel; namespace chromeos { -class WrenchMenuUI : public MenuUI { +class WrenchMenuUI : public MenuUI, + public NotificationObserver { public: explicit WrenchMenuUI(TabContents* contents); // MenuUI overrides: + virtual void ModelUpdated(const menus::MenuModel* new_model); virtual void AddCustomConfigValues(DictionaryValue* config) const; + // NotificationObserver: + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + // Updates zoom controls to reflect the current zooming state. + void UpdateZoomControls(); + // A convenient factory method to create Menu2 for wrench menu. static views::Menu2* CreateMenu2(menus::MenuModel* model); private: + NotificationRegistrar registrar_; + DISALLOW_COPY_AND_ASSIGN(WrenchMenuUI); }; diff --git a/chrome/browser/resources/menu.css b/chrome/browser/resources/menu.css index a279618..2c9b055 100644 --- a/chrome/browser/resources/menu.css +++ b/chrome/browser/resources/menu.css @@ -10,10 +10,13 @@ body { .menu-item { white-space: nowrap; margin: 0; - padding-top: 0; - padding-bottom: 0; + padding-top: 1px; + padding-bottom: 1px; background-repeat: no-repeat; -webkit-padding-end: 19px; + display: -webkit-box; + -webkit-box-orient: horizontal; + overflow: hidden; } .disabled { @@ -25,10 +28,15 @@ body { } .menu-label { - display: inline-block; + -webkit-box-flex: 1; vertical-align: middle; } +.accelerator { + opacity: 0.3; + -webkit-margin-start: 15px; +} + .left-icon { background-position: 4px center; } diff --git a/chrome/browser/resources/menu.html b/chrome/browser/resources/menu.html index f35e2fd..f6acc6a 100644 --- a/chrome/browser/resources/menu.html +++ b/chrome/browser/resources/menu.html @@ -6,6 +6,7 @@ <script src="shared/js/cr.js"></script> <script src="shared/js/cr/ui.js"></script> <script src="menu.js"></script> + <style i18n-content="menu_css"></style> <script i18n-content="menu_source"></script> </head> <body> diff --git a/chrome/browser/resources/menu.js b/chrome/browser/resources/menu.js index 67e34fa..805550c 100644 --- a/chrome/browser/resources/menu.js +++ b/chrome/browser/resources/menu.js @@ -15,9 +15,14 @@ var MNEMONIC_REGEXP = /([^&]*)&(.)(.*)/; /** * Sends 'activate' DOMUI message. + * @param {number} index The index of menu item to activate in menu model. + * @param {string} mode The activation mode, one of 'close_and_activate', or + * 'activate_no_close'. + * TODO(oshima): change these string to enum numbers once it becomes possible + * to pass number to C++. */ -function sendActivate(index) { - chrome.send('activate', [String(index), 'close_and_activate']); +function sendActivate(index, mode) { + chrome.send('activate', [String(index), mode]); } /** @@ -42,9 +47,11 @@ MenuItem.prototype = { * @param {Object} attrs JSON object that represents this menu items * properties. This is created from menu model in C code. See * chromeos/views/native_menu_domui.cc. - * @param {number} leftIconWidth The left icon's width. 0 if no icon. + * @param {Object} model The model object. */ - init: function(menu, attrs, leftIconWidth) { + init: function(menu, attrs, model) { + // The left icon's width. 0 if no icon. + var leftIconWidth = model.maxIconWidth; this.menu_ = menu; this.attrs = attrs; var attrs = this.attrs; @@ -54,24 +61,27 @@ MenuItem.prototype = { attrs.type == 'submenu' || attrs.type == 'check' || attrs.type == 'radio') { - this.initMenuItem_(leftIconWidth); + this.initMenuItem_(); + this.initPadding_(leftIconWidth); } else { + // This should not happend. this.classList.add('disabled'); this.textContent = 'unknown'; } - this.classList.add(leftIconWidth ? 'has-icon' : 'no-icon'); - if (attrs.visible) { - menu.appendChild(this); + menu.appendChild(this); + if (!attrs.visible) { + this.classList.add('hidden'); } }, /** * Changes the selection state of the menu item. - * @param {boolean} b True to set the selection, or false otherwise. + * @param {boolean} selected True to set the selection, or false + * otherwise. */ - set selected(b) { - if (b) { + set selected(selected) { + if (selected) { this.classList.add('selected'); this.menu_.selectedItem = this; } else { @@ -87,7 +97,8 @@ MenuItem.prototype = { this.menu_.openSubmenu(this); } else if (this.attrs.type != 'separator' && this.className.indexOf('selected') >= 0) { - sendActivate(this.menu_.getMenuItemIndexOf(this)); + sendActivate(this.menu_.getMenuItemIndexOf(this), + 'close_and_activate'); } }, @@ -104,56 +115,30 @@ MenuItem.prototype = { * Internal method to initiailze the MenuItem. * @private */ - initMenuItem_: function(leftIconWidth) { + initMenuItem_: function() { var attrs = this.attrs; this.className = 'menu-item ' + attrs.type; - this.menu_.addHandlers(this); - var mnemonic = MNEMONIC_REGEXP.exec(attrs.label); - if (mnemonic) { - var c = mnemonic[2]; - this.menu_.registerMnemonicKey(c, this); - } - if (leftIconWidth > 0) { - this.classList.add('left-icon'); - - var url; - if (attrs.type == 'radio') { - url = attrs.checked ? - this.menu_.config_.radioOnUrl : - this.menu_.config_.radioOffUrl; - } else if (attrs.icon) { - url = attrs.icon; - } else if (attrs.type == 'check' && attrs.checked) { - url = this.menu_.config_.checkUrl; - } - if (url) { - this.style.backgroundImage = 'url(' + url + ')'; - } - // TODO(oshima): figure out how to update left padding in rule. - // 4 is the padding on left side of icon. - var padding = - 4 + leftIconWidth + this.menu_.config_.icon_to_label_padding; - this.style.paddingLeft = padding + 'px'; - } + this.menu_.addHandlers(this, this); var label = document.createElement('div'); label.className = 'menu-label'; - - if (!mnemonic) { - label.textContent = attrs.label; - } else { - label.appendChild(document.createTextNode(mnemonic[1])); - label.appendChild(document.createElement('span')); - label.appendChild(document.createTextNode(mnemonic[3])); - label.childNodes[1].className = 'mnemonic'; - label.childNodes[1].textContent = mnemonic[2]; - } + this.menu_.addLabelTo(this, attrs.label, label, + true /* enable mnemonic */); if (attrs.font) { label.style.font = attrs.font; } this.appendChild(label); + + if (attrs.accel) { + var accel = document.createElement('div'); + accel.className = 'accelerator'; + accel.textContent = attrs.accel; + accel.style.font = attrs.font; + this.appendChild(accel); + } + if (attrs.type == 'submenu') { // This overrides left-icon's position, but it's OK as submenu // shoudln't have left-icon. @@ -161,6 +146,34 @@ MenuItem.prototype = { this.style.backgroundImage = 'url(' + this.menu_.config_.arrowUrl + ')'; } }, + + initPadding_: function(leftIconWidth) { + if (leftIconWidth <= 0) { + this.classList.add('no-icon'); + return; + } + this.classList.add('left-icon'); + + var url; + var attrs = this.attrs; + if (attrs.type == 'radio') { + url = attrs.checked ? + this.menu_.config_.radioOnUrl : + this.menu_.config_.radioOffUrl; + } else if (attrs.icon) { + url = attrs.icon; + } else if (attrs.type == 'check' && attrs.checked) { + url = this.menu_.config_.checkUrl; + } + if (url) { + this.style.backgroundImage = 'url(' + url + ')'; + } + // TODO(oshima): figure out how to update left padding in rule. + // 4 is the padding on left side of icon. + var padding = + 4 + leftIconWidth + this.menu_.config_.icon_to_label_padding; + this.style.WebkitPaddingStart = padding + 'px'; + }, }; /** @@ -274,6 +287,37 @@ Menu.prototype = { }, /** + * Adds a label to {@code targetDiv}. A label may contain + * mnemonic key, preceded by '&'. + * @param {MenuItem} item The menu item to be activated by mnemonic + * key. + * @param {string} label The label string to be added to + * {@code targetDiv}. + * @param {HTMLElement} div The div element the label is added to. + * @param {boolean} enableMnemonic True to enable mnemonic, or false + * to not to interprete mnemonic key. The function removes '&' + * from the label in both cases. + */ + addLabelTo: function(item, label, targetDiv, enableMnemonic) { + var mnemonic = MNEMONIC_REGEXP.exec(label); + if (mnemonic && enableMnemonic) { + var c = mnemonic[2].toLowerCase(); + this.mnemonics_[c] = item; + } + if (!mnemonic) { + targetDiv.textContent = label; + } else if (enableMnemonic) { + targetDiv.appendChild(document.createTextNode(mnemonic[1])); + targetDiv.appendChild(document.createElement('span')); + targetDiv.appendChild(document.createTextNode(mnemonic[3])); + targetDiv.childNodes[1].className = 'mnemonic'; + targetDiv.childNodes[1].textContent = mnemonic[2]; + } else { + targetDiv.textContent = mnemonic.splice(1, 3).join(''); + } + }, + + /** * Returns the index of the {@code item}. */ getMenuItemIndexOf: function(item) { @@ -281,10 +325,12 @@ Menu.prototype = { }, /** - * A template method to create MenuItem object. - * Subclass class can override to return custom menu item. + * A template method to create an item object. It can be a subclass + * of MenuItem, or any HTMLElement that implements {@code init}, + * {@code activate} methods as well as {@code selected} attribute. + * @param {Object} attrs The menu item's properties passed from C++. */ - createMenuItem: function() { + createMenuItem: function(attrs) { return new MenuItem(); }, @@ -301,7 +347,7 @@ Menu.prototype = { for (var i = 0; i < model.items.length; i++) { var attrs = model.items[i]; var item = this.createMenuItem(attrs); - item.init(this, attrs, model.maxIconWidth); + item.init(this, attrs, model); this.items_.push(item); } this.onResize_(); @@ -320,28 +366,19 @@ Menu.prototype = { }, /** - * Registers mnemonic key. - * @param {string} c A mnemonic key to activate item. - * @param {MenuItem} item An item to be activated when {@code c} is pressed. - */ - registerMnemonicKey: function(c, item) { - this.mnemonics_[c.toLowerCase()] = item; - }, - - /** * Add event handlers for the item. */ - addHandlers: function(item) { + addHandlers: function(item, target) { var menu = this; - item.addEventListener('mouseover', function(event) { + target.addEventListener('mouseover', function(event) { menu.onMouseover_(event, item); }); if (item.attrs.enabled) { - item.addEventListener('mouseup', function(event) { + target.addEventListener('mouseup', function(event) { menu.onClick_(event, item); }); } else { - item.classList.add('disabled'); + target.classList.add('disabled'); } }, @@ -395,7 +432,7 @@ Menu.prototype = { /** * Open submenu {@code item}. It does nothing if the submenu is * already opened. - * @param {MenuItem} item the submenu item to open. + * @param {MenuItem} item The submenu item to open. */ openSubmenu: function(item) { this.cancelSubmenuTimer_(); @@ -462,8 +499,10 @@ Menu.prototype = { // Handles mnemonic. var c = String.fromCharCode(event.keyCode); var item = this.mnemonics_[c.toLowerCase()]; - if (item) + if (item) { + item.selected = true; item.activate(); + } }, // Mouse Event handlers @@ -556,7 +595,7 @@ Menu.prototype = { * 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 + * @param {number} incr Specifies the direction to search, 1 to * downwards and -1 for upwards. * @private */ @@ -571,7 +610,8 @@ Menu.prototype = { for (var i = 0; i < len; i++) { index = (index + incr + len) % len; var item = this.items_[index]; - if (item.attrs.enabled && item.attrs.type != 'separator') + if (item.attrs.enabled && item.attrs.type != 'separator' && + !item.classList.contains('hidden')) return item; } return null; @@ -582,32 +622,26 @@ Menu.prototype = { * @private */ cancelSubmenuTimer_: function() { - if (this.openSubmenuTimer_) { - clearTimeout(this.openSubmenuTimer_); - this.openSubmenuTimer_ = 0; - } - if (this.closeSubmenuTimer_) { - clearTimeout(this.closeSubmenuTimer_); - this.closeSubmenuTimer_ = 0; - } + clearTimeout(this.openSubmenuTimer_); + this.openSubmenuTimer_ = 0; + clearTimeout(this.closeSubmenuTimer_); + this.closeSubmenuTimer_ = 0; }, /** * Starts auto scroll. - * @param {number} tick the number of pixels to scroll. + * @param {number} tick The number of pixels to scroll. * @private */ autoScroll_: function(tick) { var previous = this.scrollTop; this.scrollTop += tick; - if (this.scrollTop != previous) { - var menu = this; - this.scrollTimer_ = setTimeout( - function() { - menu.autoScroll_(tick); - }, - SCROLL_INTERVAL_MS); - } + var menu = this; + this.scrollTimer_ = setTimeout( + function() { + menu.autoScroll_(tick); + }, + SCROLL_INTERVAL_MS); }, /** @@ -615,10 +649,8 @@ Menu.prototype = { * @private */ stopScroll_: function () { - if (this.scrollTimer_) { - clearTimeout(this.scrollTimer_); - this.scrollTimer_ = 0; - } + clearTimeout(this.scrollTimer_); + this.scrollTimer_ = 0; }, /** diff --git a/chrome/browser/resources/wrench_menu.css b/chrome/browser/resources/wrench_menu.css new file mode 100644 index 0000000..b696068 --- /dev/null +++ b/chrome/browser/resources/wrench_menu.css @@ -0,0 +1,43 @@ +.fullscreen { + background-image: url('../../app/theme/fullscreen_menu_button.png'); + background-position: center center; + background-repeat: no-repeat; + border: solid 1px rgba(0, 0, 0, 0.3); + border-radius: 3px; + width: 23px; + height: 23px; + -webkit-margin-start: 4px; +} + +.edit-button { + padding: 2px; + text-align: center; + min-width: 35px; +} + +.left-button { + border: solid 1px rgba(0, 0, 0, 0.3); + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} + +.right-button { + border: solid 1px rgba(0, 0, 0, 0.3); + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +.center-button { + border-top: solid 1px rgba(0, 0, 0, 0.3); + border-bottom: solid 1px rgba(0, 0, 0, 0.3); +} + +.zoom-button { + text-align: center; + width: 15px; +} + +.zoom-percent { + min-width: 40px; + text-align: center; +} diff --git a/chrome/browser/resources/wrench_menu.js b/chrome/browser/resources/wrench_menu.js index c45e432..8566270 100644 --- a/chrome/browser/resources/wrench_menu.js +++ b/chrome/browser/resources/wrench_menu.js @@ -2,4 +2,310 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// T.B.D. +/** + * ButtonCommand class for small buttons on menu items. + */ +var ButtonCommand = cr.ui.define('div'); + +ButtonCommand.prototype = { + __proto__: HTMLDivElement.prototype, + + /** + * Decorate Button item. + */ + decorate: function() { + }, + + /** + * Changes the selection state of the menu item. + * @param {boolean} selected True to set the selection, or false otherwise. + */ + set selected(selected) { + if (selected) { + this.classList.add('selected'); + this.menu_.selectedItem = this; + } else { + this.classList.remove('selected'); + } + }, + + /** + * Activate the menu item. + */ + activate: function() { + sendActivate(this.menu_.getMenuItemIndexOf(this), + 'close_and_activate'); + }, +}; + +/** + * EditCommand implements Copy and Paste command. + */ +var EditCommand = cr.ui.define('div'); + +EditCommand.prototype = { + __proto__: ButtonCommand.prototype, + + /** + * Initialize the menu item. + * @override + */ + init: function(menu, attrs, model) { + this.menu_ = menu; + this.attrs = attrs; + if (this.attrs.font) { + this.style.font = attrs.font; + } + menu.addHandlers(this, this); + if (attrs.command_id == menu.config_.IDC_COPY) { + menu.addLabelTo(this, menu.config_.IDS_COPY, this, + false /* no mnemonic */); + } else { + menu.addLabelTo(this, menu.config_.IDS_PASTE, this, + false /* no mnemonic */); + } + }, +}; + +/** + * EditMenuItem which has Copy and Paste commands inside. + */ +var EditMenuItem = cr.ui.define('div'); + +EditMenuItem.prototype = { + __proto__: MenuItem.prototype, + + /** + * Initialize + */ + decorate: function() { + this.className = 'menu-item'; + this.label_ = document.createElement('div'); + this.label_.className = 'menu-label'; + this.cut_ = document.createElement('div'); + this.cut_.className = 'edit-button left-button'; + this.copy_ = new EditCommand(); + this.copy_.className = 'edit-button center-button'; + this.paste_ = new EditCommand(); + this.paste_.className = 'edit-button right-button'; + + this.appendChild(this.label_); + this.appendChild(this.cut_); + this.appendChild(this.copy_); + this.appendChild(this.paste_); + }, + + /** + * Activates the command. + * @override + */ + activate: function() { + sendActivate(this.menu_.getMenuItemIndexOf(this), + 'close_and_activate'); + }, + + /** + * @override + */ + set selected(selected) { + if (selected) { + this.cut_.classList.add('selected'); + this.menu_.selectedItem = this; + } else { + this.cut_.classList.remove('selected'); + } + }, + + /** + * Initialize the edit items with configuration info. + * @override + */ + initMenuItem_: function() { + this.label_.textContent = + this.menu_.config_.IDS_EDIT2; + if (this.attrs.font) { + this.label_.style.font = this.attrs.font; + this.cut_.style.font = this.attrs.font; + } + this.menu_.addLabelTo( + this, this.menu_.config_.IDS_CUT, this.cut_, + false /* no mnemonic */); + this.menu_.addHandlers(this, this.cut_); + }, +}; + +/** + * ZoomCommand class implements Zoom plus and fullscreen. + */ +var ZoomCommand = cr.ui.define('div'); + +ZoomCommand.prototype = { + __proto__: ButtonCommand.prototype, + + /** + * Initialize the menu item. + * @override + */ + init: function(menu, attrs, model) { + this.menu_ = menu; + this.attrs = attrs; + menu.addHandlers(this, this); + if (attrs.command_id == menu.config_.IDC_ZOOM_PLUS) { + this.textContent = '+'; + } + if (this.attrs.font) { + this.style.font = attrs.font; + } + }, + + /** + * Activate zoom plus and full screen commands. + * @override + */ + activate: function() { + sendActivate(this.menu_.getMenuItemIndexOf(this), + this.attrs.command_id == this.menu_.config_.IDC_ZOOM_PLUS ? + 'activate_no_close' : 'close_and_activate'); + }, +}; + +/** + * ZoomMenuItem which has plus and fullscreen buttons inside. + */ +var ZoomMenuItem = cr.ui.define('div'); + +ZoomMenuItem.prototype = { + __proto__: MenuItem.prototype, + + /** + * Decorate Zoom button item. + */ + decorate: function() { + this.className = 'menu-item'; + + this.label_ = document.createElement('div'); + this.label_.className = 'menu-label'; + this.minus_ = document.createElement('div'); + this.minus_.className = 'zoom-button left-button'; + this.minus_.textContent = '-'; + this.plus_ = new ZoomCommand(); + this.plus_.className = 'zoom-button right-button'; + this.percent_ = document.createElement('div'); + this.percent_.className = 'zoom-percent center-button'; + this.fullscreen_ = new ZoomCommand(); + this.fullscreen_.className = 'fullscreen'; + + this.appendChild(this.label_); + this.appendChild(this.minus_); + this.appendChild(this.percent_); + this.appendChild(this.plus_); + this.appendChild(this.fullscreen_); + }, + + /** + * Activates the cut command. + * @override + */ + activate: function() { + sendActivate(this.menu_.getMenuItemIndexOf(this), + 'activate_no_close'); + }, + + /** + * Updates zoom controls. + * @params {JSON} params JSON object to configure zoom controls. + */ + updateZoomControls: function(params) { + this.attrs.enabled = params.plus; + if (params.plus) { + this.plus_.classList.remove('disabled'); + } else { + this.plus_.classList.add('disabled'); + } + this.attrs.enabled = params.minus; + if (params.minus) { + this.classList.remove('disabled'); + } else { + this.classList.add('disabled'); + } + this.percent_.textContent = params.percent; + }, + + /** + * @override + */ + set selected(selected) { + if (selected) { + this.minus_.classList.add('selected'); + this.menu_.selectedItem = this; + } else { + this.minus_.classList.remove('selected'); + } + }, + + /** + * Initializes the zoom menu item with configuration info. + * @override + */ + initMenuItem_: function() { + this.label_.textContent = + this.menu_.config_.IDS_ZOOM_MENU2; + this.menu_.addHandlers(this, this.minus_); + + if (this.attrs.font) { + this.label_.style.font = this.attrs.font; + this.minus_.style.font = this.attrs.font; + this.percent_.style.font = this.attrs.font; + } + }, +}; + +/** + * WrenchMenu + */ +var WrenchMenu = cr.ui.define('div'); + +WrenchMenu.prototype = { + __proto__: Menu.prototype, + + /** + * Decorate Zoom button item. + */ + decorate: function() { + Menu.prototype.decorate.call(this); + this.edit_ = new EditMenuItem(); + this.zoom_ = new ZoomMenuItem(); + }, + + /** + * Create a MenuItem for given {@code attrs}. + * @override + */ + createMenuItem: function(attrs) { + switch(attrs.command_id) { + case this.config_.IDC_CUT: + return this.edit_; + case this.config_.IDC_COPY: + return this.edit_.copy_; + case this.config_.IDC_PASTE: + return this.edit_.paste_; + case this.config_.IDC_ZOOM_MINUS: + return this.zoom_; + case this.config_.IDC_ZOOM_PLUS: + return this.zoom_.plus_; + case this.config_.IDC_FULLSCREEN: + return this.zoom_.fullscreen_; + default: + return new MenuItem(); + } + }, + + updateZoomControls: function(params) { + this.zoom_.updateZoomControls(params); + }, +}; + +function updateZoomControls(params) { + var menu = document.getElementById('viewport'); + menu.updateZoomControls(params); +} diff --git a/chrome/browser/views/toolbar_view.cc b/chrome/browser/views/toolbar_view.cc index 974059a..48e7f69 100644 --- a/chrome/browser/views/toolbar_view.cc +++ b/chrome/browser/views/toolbar_view.cc @@ -22,7 +22,12 @@ #include "chrome/browser/views/browser_actions_container.h" #include "chrome/browser/views/event_utils.h" #include "chrome/browser/views/frame/browser_view.h" +#if !defined(OS_CHROMEOS) #include "chrome/browser/views/wrench_menu.h" +#else +#include "views/controls/menu/menu_2.h" +#include "chrome/browser/chromeos/dom_ui/wrench_menu_ui.h" +#endif #include "chrome/browser/wrench_menu_model.h" #include "chrome/common/notification_service.h" #include "chrome/common/pref_names.h" @@ -257,18 +262,32 @@ bool ToolbarView::GetAcceleratorInfo(int id, menus::Accelerator* accel) { //////////////////////////////////////////////////////////////////////////////// // ToolbarView, views::MenuDelegate implementation: -void ToolbarView::RunMenu(views::View* source, const gfx::Point& /*pt*/) { +void ToolbarView::RunMenu(views::View* source, const gfx::Point& /* pt */) { DCHECK_EQ(VIEW_ID_APP_MENU, source->GetID()); bool destroyed_flag = false; destroyed_flag_ = &destroyed_flag; +#if defined(OS_CHROMEOS) + wrench_menu_.reset( + chromeos::WrenchMenuUI::CreateMenu2(wrench_menu_model_.get())); +#else wrench_menu_ = new WrenchMenu(browser_); wrench_menu_->Init(wrench_menu_model_.get()); +#endif for (size_t i = 0; i < menu_listeners_.size(); ++i) menu_listeners_[i]->OnMenuOpened(); +#if defined(OS_CHROMEOS) + gfx::Point screen_loc; + views::View::ConvertPointToScreen(app_menu_, &screen_loc); + gfx::Rect bounds(screen_loc, app_menu_->size()); + // TODO(oshima): Support RTL. + wrench_menu_->RunMenuAt(gfx::Point(bounds.right(), bounds.bottom()), + views::Menu2::ALIGN_TOPRIGHT); +#else wrench_menu_->RunMenu(app_menu_); +#endif if (destroyed_flag) return; diff --git a/chrome/browser/views/toolbar_view.h b/chrome/browser/views/toolbar_view.h index e757f2d..f79ec25 100644 --- a/chrome/browser/views/toolbar_view.h +++ b/chrome/browser/views/toolbar_view.h @@ -27,7 +27,13 @@ class BrowserActionsContainer; class Browser; class Profile; +#if defined(OS_CHROMEOS) +namespace views { +class Menu2; +} // namespace views +#else class WrenchMenu; +#endif // The Browser Window's toolbar. class ToolbarView : public AccessibleToolbarView, @@ -193,7 +199,11 @@ class ToolbarView : public AccessibleToolbarView, scoped_ptr<menus::SimpleMenuModel> wrench_menu_model_; // Wrench menu. +#if defined(OS_CHROMEOS) + scoped_ptr<views::Menu2> wrench_menu_; +#else scoped_refptr<WrenchMenu> wrench_menu_; +#endif // Vector of listeners to receive callbacks when the menu opens. std::vector<views::MenuListener*> menu_listeners_; |