| Index: chrome/browser/resources/menu.js
|
| ===================================================================
|
| --- chrome/browser/resources/menu.js (revision 111700)
|
| +++ chrome/browser/resources/menu.js (working copy)
|
| @@ -1,674 +0,0 @@
|
| -// 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,
|
| -
|
| - /**
|
| - * Scrollable Viewport.
|
| - * @type {HTMLElement}
|
| - */
|
| - viewpotr_ : null,
|
| -
|
| - /**
|
| - * 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 = document.getElementById('scroll-up');
|
| - var down = document.getElementById('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('');
|
| - }
|
| - },
|
| -
|
| - /**
|
| - * Returns 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++.
|
| - */
|
| - 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 = document.getElementById('scroll-up');
|
| - var down = document.getElementById('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', []);
|
| - }
|
| - },
|
| -
|
| - /**
|
| - * Find a next selectable item. If nothing is selected, the 1st
|
| - * selectable item will be chosen. Returns null if nothing is
|
| - * selectable.
|
| - * @param {number} incr Specifies the direction to search, 1 to
|
| - * downwards and -1 for upwards.
|
| - * @private
|
| - */
|
| - findNextEnabled_: function(incr) {
|
| - 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++.
|
| - */
|
| -function init(config) {
|
| - document.getElementById('viewport').init(config);
|
| -}
|
| -
|
| -function selectItem() {
|
| - document.getElementById('viewport').showSelection();
|
| -}
|
| -
|
| -function updateModel(model) {
|
| - document.getElementById('viewport').updateModel(model);
|
| -}
|
| -
|
| -function modelUpdated() {
|
| - chrome.send('model_updated', []);
|
| -}
|
| -
|
| -function enableScroll(enabled) {
|
| - document.getElementById('viewport').scrollEnabled = enabled;
|
| -}
|
|
|