diff options
author | Andras Becsi <andras.becsi@digia.com> | 2014-03-18 13:16:26 +0100 |
---|---|---|
committer | Frederik Gladhorn <frederik.gladhorn@digia.com> | 2014-03-20 15:55:39 +0100 |
commit | 3f0f86b0caed75241fa71c95a5d73bc0164348c5 (patch) | |
tree | 92b9fb00f2e9e90b0be2262093876d4f43b6cd13 /chromium/chrome/browser/resources/chromeos/menu.js | |
parent | e90d7c4b152c56919d963987e2503f9909a666d2 (diff) | |
download | qtwebengine-chromium-3f0f86b0caed75241fa71c95a5d73bc0164348c5.tar.gz |
Update to new stable branch 1750
This also includes an updated ninja and chromium dependencies
needed on Windows.
Change-Id: Icd597d80ed3fa4425933c9f1334c3c2e31291c42
Reviewed-by: Zoltan Arvai <zarvai@inf.u-szeged.hu>
Reviewed-by: Zeno Albisser <zeno.albisser@digia.com>
Diffstat (limited to 'chromium/chrome/browser/resources/chromeos/menu.js')
-rw-r--r-- | chromium/chrome/browser/resources/chromeos/menu.js | 670 |
1 files changed, 670 insertions, 0 deletions
diff --git a/chromium/chrome/browser/resources/chromeos/menu.js b/chromium/chrome/browser/resources/chromeos/menu.js new file mode 100644 index 00000000000..598e58bfdc3 --- /dev/null +++ b/chromium/chrome/browser/resources/chromeos/menu.js @@ -0,0 +1,670 @@ +// Copyright (c) 2011 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; +// Scroll repeat interval. +var SCROLL_INTERVAL_MS = 20; +// Scrolling amount in pixel. +var SCROLL_TICK_PX = 4; +// Regular expression to match/find mnemonic key. +var MNEMONIC_REGEXP = /([^&]*)&(.)(.*)/; + +var localStrings = new LocalStrings(); + +/** + * Sends 'activate' WebUI 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, mode) { + chrome.send('activate', [String(index), mode]); +} + +/** + * MenuItem class. + */ +var MenuItem = cr.ui.define('div'); + +MenuItem.prototype = { + __proto__: HTMLDivElement.prototype, + + /** + * Decorates the menu item element. + */ + decorate: function() { + this.className = 'menu-item'; + }, + + /** + * Initialize the MenuItem. + * @param {Menu} menu A {@code Menu} object to which this menu item + * will be added to. + * @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_webui.cc. + * @param {Object} model The model object. + */ + 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; + if (attrs.type == 'separator') { + this.className = 'separator'; + } else if (attrs.type == 'command' || + attrs.type == 'submenu' || + attrs.type == 'check' || + attrs.type == 'radio') { + this.initMenuItem_(); + this.initPadding_(leftIconWidth); + } else { + // This should not happend. + this.classList.add('disabled'); + this.textContent = 'unknown'; + } + + menu.appendChild(this); + if (!attrs.visible) { + this.classList.add('hidden'); + } + }, + + /** + * 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() { + if (this.attrs.type == 'submenu') { + this.menu_.openSubmenu(this); + } else if (this.attrs.type != 'separator' && + this.className.indexOf('selected') >= 0) { + sendActivate(this.menu_.getMenuItemIndexOf(this), + 'close_and_activate'); + } + }, + + /** + * Sends open_submenu WebUI message. + */ + sendOpenSubmenuCommand: function() { + chrome.send('open_submenu', + [String(this.menu_.getMenuItemIndexOf(this)), + String(this.getBoundingClientRect().top)]); + }, + + /** + * Internal method to initiailze the MenuItem. + * @private + */ + initMenuItem_: function() { + var attrs = this.attrs; + this.className = 'menu-item ' + attrs.type; + this.menu_.addHandlers(this, this); + var label = document.createElement('div'); + + label.className = 'menu-label'; + 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. + this.classList.add('right-icon'); + 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'; + }, +}; + +/** + * Menu class. + */ +var Menu = cr.ui.define('div'); + +Menu.prototype = { + __proto__: HTMLDivElement.prototype, + + /** + * Configuration object. + * @type {Object} + */ + config_: null, + + /** + * Currently selected menu item. + * @type {MenuItem} + */ + current_: null, + + /** + * Timers for opening/closing submenu. + * @type {number} + */ + openSubmenuTimer_: 0, + closeSubmenuTimer_: 0, + + /** + * Auto scroll timer. + * @type {number} + */ + scrollTimer_: 0, + + /** + * Pointer to a submenu currently shown, if any. + * @type {MenuItem} + */ + submenuShown_: null, + + /** + * True if this menu is root. + * @type {boolean} + */ + isRoot_: false, + + /** + * Total hight of scroll buttons. Used to adjust the height of + * viewport in order to show scroll bottons without scrollbar. + * @type {number} + */ + buttonHeight_: 0, + + /** + * True to enable scroll button. + * @type {boolean} + */ + scrollEnabled: false, + + /** + * Decorates the menu element. + */ + decorate: function() { + this.id = 'viewport'; + }, + + /** + * Initialize the menu. + * @param {Object} config Configuration parameters in JSON format. + * See chromeos/views/native_menu_webui.cc for details. + */ + init: function(config) { + // List of menu items + this.items_ = []; + // Map from mnemonic character to item to activate + this.mnemonics_ = {}; + + this.config_ = config; + this.addEventListener('mouseout', this.onMouseout_.bind(this)); + + document.addEventListener('keydown', this.onKeydown_.bind(this)); + document.addEventListener('keypress', this.onKeypress_.bind(this)); + document.addEventListener('mousewheel', this.onMouseWheel_.bind(this)); + window.addEventListener('resize', this.onResize_.bind(this)); + + // Setup scroll events. + var up = $('scroll-up'); + var down = $('scroll-down'); + up.addEventListener('mouseout', this.stopScroll_.bind(this)); + down.addEventListener('mouseout', this.stopScroll_.bind(this)); + var menu = this; + up.addEventListener('mouseover', + function() { + menu.autoScroll_(-SCROLL_TICK_PX); + }); + down.addEventListener('mouseover', + function() { + menu.autoScroll_(SCROLL_TICK_PX); + }); + + this.buttonHeight_ = + up.getBoundingClientRect().height + + down.getBoundingClientRect().height; + }, + + /** + * 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(''); + } + }, + + /** + * @return {number} The index of the {@code item}. + */ + getMenuItemIndexOf: function(item) { + return this.items_.indexOf(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++. + * @return {MenuItem} The created menu item. + */ + createMenuItem: function(attrs) { + return new MenuItem(); + }, + + /** + * Update and display the new model. + */ + updateModel: function(model) { + this.isRoot = model.isRoot; + this.current_ = null; + this.items_ = []; + this.mnemonics_ = {}; + this.innerHTML = ''; // remove menu items + + for (var i = 0; i < model.items.length; i++) { + var attrs = model.items[i]; + var item = this.createMenuItem(attrs); + item.init(this, attrs, model); + this.items_.push(item); + } + this.onResize_(); + }, + + /** + * Highlights the currently selected item, or + * select the 1st selectable item if none is selected. + */ + showSelection: function() { + if (this.current_) { + this.current_.selected = true; + } else { + this.findNextEnabled_(1).selected = true; + } + }, + + /** + * Add event handlers for the item. + */ + addHandlers: function(item, target) { + var menu = this; + target.addEventListener('mouseover', function(event) { + menu.onMouseover_(event, item); + }); + if (item.attrs.enabled) { + target.addEventListener('mouseup', function(event) { + menu.onClick_(event, item); + }); + } else { + target.classList.add('disabled'); + } + }, + + /** + * Set the selected item. This controls timers to open/close submenus. + * 1) If the selected menu is submenu, and that submenu is not yet opeend, + * start timer to open. This will not cancel close timer, so + * if there is a submenu opened, it will be closed before new submenu is + * open. + * 2) If the selected menu is submenu, and that submenu is already opened, + * cancel both open/close timer. + * 3) If the selected menu is not submenu, cancel all timers and start + * timer to close submenu. + * This prevents from opening/closing menus while you're actively + * navigating menus. To open submenu, you need to wait a bit, or click + * submenu. + * + * @param {MenuItem} item The selected item. + */ + set selectedItem(item) { + if (this.current_ != item) { + if (this.current_ != null) + this.current_.selected = false; + this.current_ = item; + this.makeSelectedItemVisible_(); + } + + var menu = this; + if (item.attrs.type == 'submenu') { + if (this.submenuShown_ != item) { + this.openSubmenuTimer_ = + setTimeout( + function() { + menu.openSubmenu(item); + }, + SUBMENU_OPEN_DELAY_MS); + } else { + this.cancelSubmenuTimer_(); + } + } else if (this.submenuShown_) { + this.cancelSubmenuTimer_(); + this.closeSubmenuTimer_ = + setTimeout( + function() { + menu.closeSubmenu_(item); + }, + SUBMENU_CLOSE_DELAY_MS); + } + }, + + /** + * Open submenu {@code item}. It does nothing if the submenu is + * already opened. + * @param {MenuItem} item The submenu item to open. + */ + openSubmenu: function(item) { + this.cancelSubmenuTimer_(); + if (this.submenuShown_ != item) { + this.submenuShown_ = item; + item.sendOpenSubmenuCommand(); + } + }, + + /** + * Handle keyboard navigation and activation. + * @private + */ + onKeydown_: function(event) { + switch (event.keyIdentifier) { + case 'Left': + this.moveToParent_(); + break; + case 'Right': + this.moveToSubmenu_(); + break; + case 'Up': + this.classList.add('mnemonic-enabled'); + this.findNextEnabled_(-1).selected = true; + break; + case 'Down': + this.classList.add('mnemonic-enabled'); + this.findNextEnabled_(1).selected = true; + break; + case 'U+0009': // tab + break; + case 'U+001B': // escape + chrome.send('close_all'); + break; + case 'Enter': + case 'U+0020': // space + if (this.current_) { + this.current_.activate(); + } + break; + } + }, + + /** + * Handle mnemonic keys. + * @private + */ + onKeypress_: function(event) { + // Handles mnemonic. + var c = String.fromCharCode(event.keyCode); + var item = this.mnemonics_[c.toLowerCase()]; + if (item) { + item.selected = true; + item.activate(); + } + }, + + // Mouse Event handlers + onClick_: function(event, item) { + item.activate(); + }, + + onMouseover_: function(event, item) { + this.cancelSubmenuTimer_(); + // Ignore false mouseover event at (0,0) which is + // emitted when opening submenu. + if (item.attrs.enabled && event.clientX != 0 && event.clientY != 0) { + item.selected = true; + } + }, + + onMouseout_: function(event) { + if (this.current_) { + this.current_.selected = false; + } + }, + + onResize_: function() { + var up = $('scroll-up'); + var down = $('scroll-down'); + // this needs to be < 2 as empty page has height of 1. + if (window.innerHeight < 2) { + // menu window is not visible yet. just hide buttons. + up.classList.add('hidden'); + down.classList.add('hidden'); + return; + } + // Do not use screen width to determin if we need scroll buttons + // as the max renderer hight can be shorter than actual screen size. + // TODO(oshima): Fix this when we implement transparent renderer. + if (this.scrollHeight > window.innerHeight && this.scrollEnabled) { + this.style.height = (window.innerHeight - this.buttonHeight_) + 'px'; + up.classList.remove('hidden'); + down.classList.remove('hidden'); + } else { + this.style.height = ''; + up.classList.add('hidden'); + down.classList.add('hidden'); + } + }, + + onMouseWheel_: function(event) { + var delta = event.wheelDelta / 5; + this.scrollTop -= delta; + }, + + /** + * Closes the submenu. + * a submenu. + * @private + */ + closeSubmenu_: function(item) { + this.submenuShown_ = 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.isRoot) { + if (this.current_) { + this.current_.selected = false; + } + 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'); + } + }, + + /** + * Finds the next selectable item. If nothing is selected, the first + * 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 + * @return {MenuItem} The next selectable item. + */ + findNextEnabled_: function(incr) { + var len = this.items_.length; + var index; + if (this.current_) { + index = this.getMenuItemIndexOf(this.current_); + } else { + index = incr > 0 ? -1 : len; + } + 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' && + !item.classList.contains('hidden')) + return item; + } + return null; + }, + + /** + * Cancels timers to open/close submenus. + * @private + */ + cancelSubmenuTimer_: function() { + clearTimeout(this.openSubmenuTimer_); + this.openSubmenuTimer_ = 0; + clearTimeout(this.closeSubmenuTimer_); + this.closeSubmenuTimer_ = 0; + }, + + /** + * Starts auto scroll. + * @param {number} tick The number of pixels to scroll. + * @private + */ + autoScroll_: function(tick) { + var previous = this.scrollTop; + this.scrollTop += tick; + var menu = this; + this.scrollTimer_ = setTimeout( + function() { + menu.autoScroll_(tick); + }, + SCROLL_INTERVAL_MS); + }, + + /** + * Stops auto scroll. + * @private + */ + stopScroll_: function() { + clearTimeout(this.scrollTimer_); + this.scrollTimer_ = 0; + }, + + /** + * Scrolls the viewport to make the selected item visible. + * @private + */ + makeSelectedItemVisible_: function() { + this.current_.scrollIntoViewIfNeeded(false); + }, +}; + +/** + * functions to be called from C++. + * @param {Object} config The viewport configuration. + */ +function init(config) { + $('viewport').init(config); +} + +function selectItem() { + $('viewport').showSelection(); +} + +function updateModel(model) { + $('viewport').updateModel(model); +} + +function modelUpdated() { + chrome.send('model_updated'); +} + +function enableScroll(enabled) { + $('viewport').scrollEnabled = enabled; +} |