summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/resources/chromeos/menu.js
diff options
context:
space:
mode:
authorAndras Becsi <andras.becsi@digia.com>2014-03-18 13:16:26 +0100
committerFrederik Gladhorn <frederik.gladhorn@digia.com>2014-03-20 15:55:39 +0100
commit3f0f86b0caed75241fa71c95a5d73bc0164348c5 (patch)
tree92b9fb00f2e9e90b0be2262093876d4f43b6cd13 /chromium/chrome/browser/resources/chromeos/menu.js
parente90d7c4b152c56919d963987e2503f9909a666d2 (diff)
downloadqtwebengine-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.js670
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;
+}