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/browser/resources | |
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/browser/resources')
-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 |
5 files changed, 487 insertions, 97 deletions
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); +} |