chromium/third_party/google_input_tools/src/chrome/os/inputview/elements/content/menuview.js

// Copyright 2014 The ChromeOS IME Authors. All Rights Reserved.
// limitations under the License.
// See the License for the specific language governing permissions and
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// distributed under the License is distributed on an "AS-IS" BASIS,
// Unless required by applicable law or agreed to in writing, software
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// You may obtain a copy of the License at
// you may not use this file except in compliance with the License.
// Licensed under the Apache License, Version 2.0 (the "License");
//
goog.provide('i18n.input.chrome.inputview.elements.content.MenuView');

goog.require('goog.a11y.aria');
goog.require('goog.a11y.aria.State');
goog.require('goog.array');
goog.require('goog.dom.TagName');
goog.require('goog.dom.classlist');
goog.require('goog.log');
goog.require('goog.style');
goog.require('i18n.input.chrome.ElementType');
goog.require('i18n.input.chrome.inputview.Css');
goog.require('i18n.input.chrome.inputview.elements.Element');
goog.require('i18n.input.chrome.inputview.elements.content.MenuItem');
goog.require('i18n.input.chrome.inputview.util');



goog.scope(function() {
var ElementType = i18n.input.chrome.ElementType;
var MenuItem = i18n.input.chrome.inputview.elements.content.MenuItem;
var Css = i18n.input.chrome.inputview.Css;


/**
 * The view for IME switcher, layout switcher and settings menu popup.
 * TODO(bshe): Refactor MenuView to extend AltDataView.
 *
 * @param {goog.events.EventTarget=} opt_eventTarget The parent event target.
 * @constructor
 * @extends {i18n.input.chrome.inputview.elements.Element}
 */
i18n.input.chrome.inputview.elements.content.MenuView = function(
    opt_eventTarget) {
  goog.base(this, '', ElementType.MENU_VIEW, opt_eventTarget);

  /**
   * The cover element.
   * Note: The reason we use a separate cover element instead of the view is
   * because of the opacity. We can not reassign the opacity in child element.
   *
   * @private {!Element}
   */
  this.coverElement_ = this.getDomHelper().createDom(goog.dom.TagName.DIV,
      Css.ALTDATA_COVER);

  /**
   * The key triggered this menu view.
   *
   * @private {i18n.input.chrome.inputview.elements.content.SoftKey}
   */
  this.triggeredBy_ = null;

  /**
   * Logger for MenuView.
   * @private {goog.log.Logger}
   */
  this.logger_ = goog.log.getLogger(
      'i18n.input.chrome.inputview.elements.content.MenuView');

  this.pointerConfig.stopEventPropagation = false;
  this.pointerConfig.preventDefault = false;
};
goog.inherits(i18n.input.chrome.inputview.elements.content.MenuView,
    i18n.input.chrome.inputview.elements.Element);
var MenuView = i18n.input.chrome.inputview.elements.content.MenuView;


/**
 * The commands which items in MenuView may send to inputview/controller.js.
 *
 * @enum {number}
 */
MenuView.Command = {
  SWITCH_IME: 0,
  SWITCH_KEYSET: 1,
  OPEN_EMOJI: 2,
  OPEN_HANDWRITING: 3,
  OPEN_SETTING: 4
};


/**
 * The maximal number of visible input methods in the view. If more than 3 input
 * methods are enabled, only 3 of them will show and others can be scrolled into
 * view. The reason we have this limitation is because menu view can not be
 * higher than the keyboard view.
 *
 * @private {number}
 */
MenuView.MAX_VISIBLE_ITEMS_ = 4;


/**
 * Same as MAX_VISIBLE_ITEM_ but for a11y keyboard. A11y virtual keyboard is
 * shorter than normal keyboard so 3 is used.
 *
 * @private {number}
 */
MenuView.MAX_VISIBLE_ITEMS_A11Y_ = 3;


/**
 * The width in px of the popup menu.
 * The total width including padding is 300px, the left padding is 41px.
 *
 * @private {number}
 */
MenuView.WIDTH_PX_ = 275;


/**
 * The left padding in px of the menu item.
 *
 * @private {number}
 */
MenuView.PADDING_LEFT_PX_ = 25;


/**
 * The height in px of the popup menu item.
 *
 * @private {number}
 */
MenuView.LIST_ITEM_HEIGHT_PX_ = 45;


/** @override */
MenuView.prototype.createDom = function() {
  goog.base(this, 'createDom');

  var dom = this.getDomHelper();
  var elem = this.getElement();
  goog.dom.classlist.add(elem, Css.MENU_VIEW);
  goog.a11y.aria.setState(this.coverElement_, goog.a11y.aria.State.LABEL,
      chrome.i18n.getMessage('DISMISS_MENU'));
  dom.appendChild(document.body, this.coverElement_);
  goog.style.setElementShown(this.coverElement_, false);

  this.coverElement_['view'] = this;
};


/**
 * Shows the menu view.
 * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} key The key
 *     triggered this menu view.
 * @param {!string} currentKeysetId The keyset ID that this menu key belongs to.
 * @param {boolean} isCompact True if the keyset that owns the menu key is a
 *     compact layout.
 * @param {boolean} enableCompactLayout True if the keyset that owns the menu
 *     key enabled compact layout.
 * @param {!string} currentInputMethod The current input method ID.
 * @param {?Array.<!Object<string, string, string>>} inputMethods The list of
 *     activated input methods.
 * @param {boolean} hasHwt Whether to add handwriting button.
 * @param {boolean} enableSettings Whether to add a link to settings page.
 * @param {boolean} hasEmoji Whether to enable emoji.
 * @param {boolean} isA11y Whether the current keyboard is a11y-optimized.
 */
MenuView.prototype.show = function(key, currentKeysetId, isCompact,
    enableCompactLayout, currentInputMethod, inputMethods, hasHwt,
    enableSettings, hasEmoji, isA11y) {
  var ElementType = i18n.input.chrome.ElementType;
  var dom = this.getDomHelper();
  if (key.type != ElementType.MENU_KEY) {
    goog.log.warning(this.logger_,
        'Unexpected key triggered the menu view. Key type = ' + key.type + '.');
    return;
  }
  this.triggeredBy_ = key;
  var coordinate = goog.style.getClientPosition(key.getElement());
  var x = coordinate.x;
  // y is the maximal height that menu view can have.
  var y = coordinate.y;

  goog.style.setElementShown(this.getElement(), true);
  // TODO(bshe): May not need to remove child.
  dom.removeChildren(this.getElement());

  var totalHeight = 0;
  totalHeight += this.addInputMethodItems_(currentInputMethod, inputMethods,
      isA11y);
  totalHeight += this.addLayoutSwitcherItem_(key, currentKeysetId, isCompact,
      enableCompactLayout);
  if (hasHwt || enableSettings || hasEmoji) {
    totalHeight += this.addFooterItems_(hasHwt, enableSettings, hasEmoji);
  }


  var left = x;
  // TODO(bshe): Take care of elemTop < 0. A scrollable view is probably needed.
  var elemTop = y - totalHeight;

  goog.style.setPosition(this.getElement(), left, elemTop);
  goog.style.setElementShown(this.coverElement_, true);
  this.triggeredBy_.setHighlighted(true);
};


/** Hides the menu view. */
MenuView.prototype.hide = function() {
  goog.style.setElementShown(this.getElement(), false);
  goog.style.setElementShown(this.coverElement_, false);
  if (this.triggeredBy_) {
    this.triggeredBy_.setHighlighted(false);
  }
};


/**
 * Adds the list of activated input methods.
 *
 * @param {!string} currentInputMethod The current input method ID.
 * @param {?Array.<!Object<string, string, string>>} inputMethods The list of
 *     activated input methods.
 * @param {boolean} isA11y Whether the current keyboard is a11y-optimized.
 * @return {number} The height of the ime list container.
 * @private
 */
MenuView.prototype.addInputMethodItems_ = function(currentInputMethod,
    inputMethods, isA11y) {
  var dom = this.getDomHelper();
  var container = dom.createDom(goog.dom.TagName.DIV,
      Css.IME_LIST_CONTAINER);
  var visibleItems = isA11y ?
      MenuView.MAX_VISIBLE_ITEMS_A11Y_ : MenuView.MAX_VISIBLE_ITEMS_;

  for (var i = 0; i < inputMethods.length; i++) {
    var inputMethod = inputMethods[i];
    var listItem = {
      'indicator': inputMethod['indicator'],
      'name': inputMethod['name'],
      'command':
        [MenuView.Command.SWITCH_IME, inputMethod['id']]
    };
    var ariaLabel = chrome.i18n.getMessage('SWITCH_TO_KEYBOARD_PREFIX') +
        inputMethod['name'];
    if (currentInputMethod == inputMethod['id']) {
      ariaLabel = chrome.i18n.getMessage('CURRENT_KEYBOARD_PREFIX') +
          inputMethod['name'];
    }
    var imeItem = new MenuItem(String(i), listItem, MenuItem.Type.LIST_ITEM,
        ariaLabel);
    imeItem.render(container);
    if (currentInputMethod == inputMethod['id']) {
      imeItem.check();
    }
    goog.style.setSize(imeItem.getElement(),
        MenuView.WIDTH_PX_ + MenuView.PADDING_LEFT_PX_,
        MenuView.LIST_ITEM_HEIGHT_PX_);
  }

  var containerHeight = inputMethods.length > visibleItems ?
      MenuView.LIST_ITEM_HEIGHT_PX_ * visibleItems :
      MenuView.LIST_ITEM_HEIGHT_PX_ * inputMethods.length;
  goog.style.setSize(container, MenuView.WIDTH_PX_ + MenuView.PADDING_LEFT_PX_,
      containerHeight);

  dom.appendChild(this.getElement(), container);
  return containerHeight;
};


/**
 * Add the full/compact layout switcher item. If a full layout does not have or
 * disabled compact layout, this is a noop.
 *
 * @param {!i18n.input.chrome.inputview.elements.content.SoftKey} key The key
 *     triggerred this menu view.
 * @param {!string} currentKeysetId The keyset ID that this menu key belongs to.
 * @param {boolean} isCompact True if the keyset that owns the menu key is a
 *     compact layout.
 * @param {boolean} enableCompactLayout True if the keyset that owns the menu
 *     key enabled compact layout.
 * @return {number} The height of the layout switcher item.
 * @private
 */
MenuView.prototype.addLayoutSwitcherItem_ = function(key, currentKeysetId,
    isCompact, enableCompactLayout) {
  // Some full layouts either disabled compact layout (CJK) or do not have one.
  // Do not add a layout switcher item in this case.
  if (!isCompact && !enableCompactLayout) {
    return 0;
  }
  // Adds layout switcher.
  var layoutSwitcherItem;
  if (isCompact) {
    var fullLayoutId = currentKeysetId.split('.')[0];
    layoutSwitcherItem = new MenuItem('MenuLayoutSwitcher',
        {
          'iconURL': 'images/regular_size.png',
          'name': chrome.i18n.getMessage('SWITCH_TO_FULL_LAYOUT'),
          'command': [MenuView.Command.SWITCH_KEYSET, fullLayoutId]
        },
        MenuItem.Type.LIST_ITEM,
        chrome.i18n.getMessage('SWITCH_TO_FULL_LAYOUT'));
  } else {
    if (goog.array.contains(i18n.input.chrome.inputview.util.KEYSETS_USE_US,
        currentKeysetId)) {
      key.toKeyset = currentKeysetId + '.compact.qwerty';
    }
    layoutSwitcherItem = new MenuItem('MenuLayoutSwitcher',
        {
          'iconURL': 'images/compact.png',
          'name': chrome.i18n.getMessage('SWITCH_TO_COMPACT_LAYOUT'),
          'command': [MenuView.Command.SWITCH_KEYSET, key.toKeyset]
        },
        MenuItem.Type.LIST_ITEM,
        chrome.i18n.getMessage('SWITCH_TO_COMPACT_LAYOUT'));
  }
  layoutSwitcherItem.render(this.getElement());
  goog.style.setSize(layoutSwitcherItem.getElement(), MenuView.WIDTH_PX_,
      MenuView.LIST_ITEM_HEIGHT_PX_);

  return MenuView.LIST_ITEM_HEIGHT_PX_;
};


/**
 * Adds a few items into the menu view. We only support handwriting and settings
 * now.
 *
 * @param {boolean} hasHwt Whether to add handwriting button.
 * @param {boolean} enableSettings Whether to add settings button.
 * @param {boolean} hasEmoji Whether to add emoji button.
 * @return {number} The height of the footer.
 * @private
 */
MenuView.prototype.addFooterItems_ = function(hasHwt, enableSettings,
    hasEmoji) {
  var dom = this.getDomHelper();
  var footer = dom.createDom(goog.dom.TagName.DIV, Css.MENU_FOOTER);
  if (hasEmoji) {
    var emoji = {
      'iconCssClass': Css.MENU_FOOTER_EMOJI_BUTTON,
      'command': [MenuView.Command.OPEN_EMOJI]
    };
    var emojiFooter = new MenuItem('emoji', emoji,
        MenuItem.Type.FOOTER_ITEM,
        chrome.i18n.getMessage('FOOTER_EMOJI_BUTTON'));
    emojiFooter.render(footer);
  }

  if (hasHwt) {
    var handWriting = {
      'iconCssClass': Css.MENU_FOOTER_HANDWRITING_BUTTON,
      'command': [MenuView.Command.OPEN_HANDWRITING]
    };
    var handWritingFooter = new MenuItem('handwriting', handWriting,
        MenuItem.Type.FOOTER_ITEM,
        chrome.i18n.getMessage('FOOTER_HANDWRITING_BUTTON'));
    handWritingFooter.render(footer);
  }

  if (enableSettings) {
    var setting = {
      'iconCssClass': Css.MENU_FOOTER_SETTING_BUTTON,
      'command': [MenuView.Command.OPEN_SETTING]
    };
    var settingFooter = new MenuItem('setting', setting,
        MenuItem.Type.FOOTER_ITEM,
        chrome.i18n.getMessage('FOOTER_SETTINGS_BUTTON'));
    settingFooter.render(footer);
  }

  // Sets footer itmes' width.
  var elems = dom.getChildren(footer);
  var len = elems.length;
  var subWidth =
      Math.ceil((MenuView.WIDTH_PX_ + MenuView.PADDING_LEFT_PX_) / len);
  for (var i = 0; i < len - 1; i++) {
    elems[i].style.width = subWidth + 'px';
  }
  elems[len - 1].style.width = (MenuView.WIDTH_PX_ + MenuView.PADDING_LEFT_PX_ -
      subWidth * (len - 1)) + 'px';

  dom.appendChild(this.getElement(), footer);
  goog.style.setSize(footer, (MenuView.WIDTH_PX_ + MenuView.PADDING_LEFT_PX_),
      MenuView.LIST_ITEM_HEIGHT_PX_);

  return MenuView.LIST_ITEM_HEIGHT_PX_;
};


/** @return {!Element} The cover element. */
MenuView.prototype.getCoverElement = function() {
  return this.coverElement_;
};


/** @override */
MenuView.prototype.resize = function(width, height) {
  goog.base(this, 'resize', width, height);

  goog.style.setSize(this.coverElement_, width, height);
  // Hides the menu view directly after resize.
  this.hide();
};


/** @override */
MenuView.prototype.disposeInternal = function() {
  this.coverElement_['view'] = null;

  goog.base(this, 'disposeInternal');
};
});  // goog.scope