chromium/third_party/google_input_tools/src/chrome/os/inputview/elements/content/candidateview.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.CandidateView');

goog.require('goog.a11y.aria');
goog.require('goog.a11y.aria.State');
goog.require('goog.dom.TagName');
goog.require('goog.dom.classlist');
goog.require('goog.object');
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.Candidate');
goog.require('i18n.input.chrome.inputview.elements.content.CandidateButton');
goog.require('i18n.input.chrome.inputview.elements.content.DragButton');
goog.require('i18n.input.chrome.inputview.elements.content.ToolbarButton');
goog.require('i18n.input.chrome.inputview.util');
goog.require('i18n.input.chrome.message.Name');



goog.scope(function() {
var Css = i18n.input.chrome.inputview.Css;
var TagName = goog.dom.TagName;
var Candidate = i18n.input.chrome.inputview.elements.content.Candidate;
var Type = i18n.input.chrome.inputview.elements.content.Candidate.Type;
var ElementType = i18n.input.chrome.ElementType;
var content = i18n.input.chrome.inputview.elements.content;
var Name = i18n.input.chrome.message.Name;
var util = i18n.input.chrome.inputview.util;



/**
 * The candidate view.
 *
 * @param {string} id The id.
 * @param {!i18n.input.chrome.inputview.Adapter} adapter .
 * @param {goog.events.EventTarget=} opt_eventTarget The event target.
 * @constructor
 * @extends {i18n.input.chrome.inputview.elements.Element}
 */
i18n.input.chrome.inputview.elements.content.CandidateView = function(id,
    adapter, opt_eventTarget) {
  goog.base(this, id, ElementType.CANDIDATE_VIEW, opt_eventTarget);

  /**
   * The bus channel to communicate with background.
   *
   * @private {!i18n.input.chrome.inputview.Adapter}
   */
  this.adapter_ = adapter;

  /**
   * The icons.
   *
   * @private {!Array.<!i18n.input.chrome.inputview.elements.Element>}
   */
  this.iconButtons_ = [];

  this.iconButtons_[IconType.BACK] = new content.CandidateButton(
      '', ElementType.BACK_BUTTON, '',
      chrome.i18n.getMessage('HANDWRITING_BACK'), this);
  this.iconButtons_[IconType.SHRINK_CANDIDATES] = new content.
      CandidateButton('', ElementType.SHRINK_CANDIDATES,
          Css.SHRINK_CANDIDATES_ICON, '', this);
  this.iconButtons_[IconType.EXPAND_CANDIDATES] = new content.
      CandidateButton('', ElementType.EXPAND_CANDIDATES,
          Css.EXPAND_CANDIDATES_ICON, '', this);
  this.iconButtons_[IconType.VOICE] = new content.CandidateButton('',
      ElementType.VOICE_BTN, Css.VOICE_MIC_BAR, '', this, true);

  /**
   * The floating virtual keyboard buttons..
   *
   * @private {!Array.<!i18n.input.chrome.inputview.elements.Element>}
   */
  this.fvkButtons_ = [];

  this.fvkButtons_.push(new content.
      DragButton('', ElementType.DRAG, Css.DRAG_BUTTON, this));

  /**
   * Toolbar buttons.
   *
   * @private {!Array.<!i18n.input.chrome.inputview.elements.Element>}
   */
  this.toolbarButtons_ = [];

  this.toolbarButtons_.push(new content.
      ToolbarButton('', ElementType.UNDO, Css.UNDO_ICON, '', this, true));
  this.toolbarButtons_.push(new content.
      ToolbarButton('', ElementType.REDO, Css.REDO_ICON, '', this, true));
  this.toolbarButtons_.push(new content.
      ToolbarButton('', ElementType.BOLD, Css.BOLD_ICON, '', this, true));
  this.toolbarButtons_.push(new content.
      ToolbarButton('', ElementType.ITALICS, Css.ITALICS_ICON, '', this, true));
  this.toolbarButtons_.push(new content.ToolbarButton(
      '', ElementType.UNDERLINE, Css.UNDERLINE_ICON, '', this, true));
  this.toolbarButtons_.push(new content.
      ToolbarButton('', ElementType.CUT, Css.CUT_ICON, '', this));
  this.toolbarButtons_.push(new content.
      ToolbarButton('', ElementType.COPY, Css.COPY_ICON, '', this));
  this.toolbarButtons_.push(new content.
      ToolbarButton('', ElementType.PASTE, Css.PASTE_ICON, '', this));
  this.toolbarButtons_.push(new content.
      ToolbarButton('', ElementType.SELECT_ALL, Css.SELECT_ALL_ICON, '', this));
};
goog.inherits(i18n.input.chrome.inputview.elements.content.CandidateView,
    i18n.input.chrome.inputview.elements.Element);
var CandidateView = i18n.input.chrome.inputview.elements.content.CandidateView;


/**
 * The icon type at the right of the candidate view.
 *
 * @enum {number}
 */
CandidateView.IconType = {
  BACK: 0,
  SHRINK_CANDIDATES: 1,
  EXPAND_CANDIDATES: 2,
  VOICE: 3
};
var IconType = CandidateView.IconType;


/**
 * The current use of the candidate view.
 *
 * @enum {number}
 */
CandidateView.CandidateViewType = {
  NONE: 0,
  CANDIDATES: 1,
  NUMBER_ROW: 2,
  TOOLTIP: 3
};
var CandidateViewType = CandidateView.CandidateViewType;


/**
 * The padding between candidates.
 *
 * @const {number}
 * @private
 */
CandidateView.PADDING_ = 50;


/**
 * The width for a candidate when showing in THREE_CANDIDATE mode.
 *
 * @const {number}
 * @private
 */
CandidateView.WIDTH_FOR_THREE_CANDIDATES_ = 200;


/**
 * The width for a candidate when showing in small resolution virtual keyboard.
 *
 * @const {number}
 * @private
 */
CandidateView.SMALL_WIDTH_FOR_THREE_CANDIDATES_ = 75;


/**
 * The width of icons in the toolbar.
 *
 * @const {number}
 * @private
 */
CandidateView.TOOLBAR_ICON_WIDTH_ = 40;


/**
 * The handwriting keyset code.
 *
 * @const {string}
 * @private
 */
CandidateView.HANDWRITING_VIEW_CODE_ = 'hwt';


/**
 * The emoji keyset code.
 *
 * @const {string}
 * @private
 */
CandidateView.EMOJI_VIEW_CODE_ = 'emoji';


/**
 * How many candidates in this view.
 *
 * @type {number}
 */
CandidateView.prototype.candidateCount = 0;


/**
 * The width in weight which stands for the entire row. It is used for the
 * alignment of the number row.
 *
 * @private {number}
 */
CandidateView.prototype.widthInWeight_ = 0;


/**
 * The width in weight of the backspace key.
 *
 * @private {number}
 */
CandidateView.prototype.backspaceWeight_ = 0;


/**
 * The width of an icon e.g voice/down/up arrow.
 *
 * @private {number}
 */
CandidateView.prototype.iconWidth_ = 120;


/**
 * The current candidate view type.
 *
 * @private {CandidateViewType}
 */
CandidateView.prototype.candidateViewType_ = CandidateViewType.NONE;


/**
 * True if trying to show the toolbar row.
 *
 * @type {boolean}
 */
CandidateView.prototype.tryShowingToolbar = false;


/** @private {string} */
CandidateView.prototype.keyset_ = '';


/** @private {boolean} */
CandidateView.prototype.isPasswordBox_ = false;


/** @private {boolean} */
CandidateView.prototype.isRTL_ = false;


/**
 * The width of the inter container.
 *
 * @private {number}
 */
CandidateView.prototype.interContainerWidth_ = 0;


/** @private {boolean} */
CandidateView.prototype.navigation_ = false;


/** @private {number} */
CandidateView.prototype.sumOfCandidates_ = 0;


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

  var dom = this.getDomHelper();
  var elem = this.getElement();
  goog.dom.classlist.add(elem, Css.CANDIDATE_VIEW);

  for (var i = 0; i < this.toolbarButtons_.length; i++) {
    var button = this.toolbarButtons_[i];
    button.render(elem);
    button.setVisible(false);
  }

  for (var i = 0; i < this.fvkButtons_.length; i++) {
    var button = this.fvkButtons_[i];
    button.render(elem);
    button.setVisible(false);
  }

  this.interContainer_ = dom.createDom(TagName.DIV,
      Css.CANDIDATE_INTER_CONTAINER);
  dom.appendChild(elem, this.interContainer_);

  for (var i = 0; i < this.iconButtons_.length; i++) {
    var button = this.iconButtons_[i];
    button.render(elem);
    button.setVisible(false);
    if (button.type == ElementType.VOICE_BTN) {
      goog.dom.classlist.add(button.getElement(), Css.VOICE_BUTTON);
    }
  }

  goog.a11y.aria.setState(/** @type {!Element} */
      (this.iconButtons_[IconType.SHRINK_CANDIDATES].getElement()),
      goog.a11y.aria.State.LABEL,
      chrome.i18n.getMessage('SHRINK_CANDIDATES'));

  goog.a11y.aria.setState(/** @type {!Element} */
      (this.iconButtons_[IconType.EXPAND_CANDIDATES].getElement()),
      goog.a11y.aria.State.LABEL,
      chrome.i18n.getMessage('EXPAND_CANDIDATES'));
};


/**
 * Hides the number row.
 */
CandidateView.prototype.hideNumberRow = function() {
  if (this.candidateViewType_ == CandidateViewType.NUMBER_ROW) {
    this.candidateViewType_ = CandidateViewType.NONE;
    this.getDomHelper().removeChildren(this.interContainer_);
  }
};


/**
 * Shows the number row.
 */
CandidateView.prototype.showNumberRow = function() {
  this.candidateViewType_ = CandidateViewType.NUMBER_ROW;
  goog.dom.classlist.remove(this.getElement(),
      i18n.input.chrome.inputview.Css.THREE_CANDIDATES);
  var dom = this.getDomHelper();
  dom.removeChildren(this.interContainer_);
  var weightArray = [];
  for (var i = 0; i < 10; i++) {
    weightArray.push(1);
  }
  weightArray.push(this.widthInWeight_ - 10);
  var values = util.splitValue(weightArray, this.width);
  for (var i = 0; i < 10; i++) {
    var candidateElem = new Candidate(String(i), goog.object.create(
        Name.CANDIDATE, String((i + 1) % 10)),
        Type.NUMBER, this.height, false, values[i], this);
    candidateElem.render(this.interContainer_);
  }
};


/**
 * Shows a tooltop in place of candidates.
 *
 * @param {string=} opt_text The text to display.
 */
CandidateView.prototype.showTooltip = function(opt_text) {
  this.clearCandidates();
  this.candidateViewType_ = CandidateViewType.TOOLTIP;
  goog.dom.classlist.remove(this.getElement(),
      i18n.input.chrome.inputview.Css.THREE_CANDIDATES);
  var candidateElem = new Candidate('tooltip', goog.object.create(
      Name.CANDIDATE, opt_text || ''),
      Type.TOOLTIP, this.height, false, this.width, this);
  candidateElem.render(this.interContainer_);
  this.switchToIcon(IconType.VOICE, false);
};


/**
 * Hides the tooltip.
 */
CandidateView.prototype.hideTooltip = function() {
  if (this.candidateViewType_ == CandidateViewType.TOOLTIP) {
    this.candidateViewType_ = CandidateViewType.NONE;
    this.getDomHelper().removeChildren(this.interContainer_);
    // Restore previous settings.
    this.updateByKeyset(this.keyset_, this.isPasswordBox_, this.isRTL_);
  }
};


/**
 * Shows the candidates.
 *
 * @param {!Array.<!Object>} candidates The candidate list.
 * @param {boolean} showThreeCandidates .
 * @param {boolean=} opt_expandable True if the candidates would be shown
 *     in expanded view.
 */
CandidateView.prototype.showCandidates = function(candidates,
    showThreeCandidates, opt_expandable) {
  this.clearCandidates();
  this.sumOfCandidates_ = candidates.length;
  if (candidates.length > 0) {
    this.candidateViewType_ = CandidateViewType.CANDIDATES;
    if (showThreeCandidates) {
      this.addThreeCandidates_(candidates);
    } else {
      this.addFullCandidates_(candidates);
      if (!this.iconButtons_[IconType.BACK].isVisible()) {
        this.switchToIcon(IconType.EXPAND_CANDIDATES,
            !!opt_expandable && this.candidateCount < candidates.length);
      }
    }
  }
};


/**
 * Adds the candidates in THREE-CANDIDATE mode.
 *
 * @param {!Array.<!Object>} candidates The candidate list.
 * @private
 */
CandidateView.prototype.addThreeCandidates_ = function(candidates) {
  goog.dom.classlist.add(this.getElement(),
      i18n.input.chrome.inputview.Css.THREE_CANDIDATES);
  this.interContainer_.style.width = 'auto';
  var num = Math.min(3, candidates.length);
  var width = CandidateView.WIDTH_FOR_THREE_CANDIDATES_;
  if (this.adapter_.isFloatingVirtualKeyboardEnabled()) {
    //TODO: large size floating virtual keyboard may still use the regular
    //width. Add an enum to distinguish small size from large and middle size
    //for floating virtual keyboard.
    width = CandidateView.SMALL_WIDTH_FOR_THREE_CANDIDATES_;
  }
  if (this.tryShowingToolbar && this.hasEnoughSpaceForToolbar_()) {
    width -= this.iconWidth_ / 3;
  }
  for (var i = 0; i < num; i++) {
    var candidateElem = new Candidate(String(i), candidates[i], Type.CANDIDATE,
        this.height, i == 1 || num == 1, width, this);
    candidateElem.render(this.interContainer_);
  }
  this.candidateCount = num;
};


/**
 * Clears the candidates.
 */
CandidateView.prototype.clearCandidates = function() {
  this.sumOfCandidates_ = 0;
  if (this.candidateViewType_ == CandidateViewType.CANDIDATES) {
    this.candidateViewType_ = CandidateViewType.NONE;
    this.candidateCount = 0;
    this.getDomHelper().removeChildren(this.interContainer_);
  }
};


/**
 * Adds candidates into the view, as many as the candidate bar can support.
 *
 * @param {!Array.<!Object>} candidates The candidate list.
 * @private
 */
CandidateView.prototype.addFullCandidates_ = function(candidates) {
  goog.dom.classlist.remove(this.getElement(),
      i18n.input.chrome.inputview.Css.THREE_CANDIDATES);
  var totalWidth = Math.floor(this.width - this.iconWidth_);
  if (this.tryShowingToolbar && this.hasEnoughSpaceForToolbar_()) {
    totalWidth -=
        (CandidateView.TOOLBAR_ICON_WIDTH_ * this.toolbarButtons_.length);
  }
  var w = 0;
  var i;
  for (i = 0; i < candidates.length; i++) {
    var candidateElem = new Candidate(String(i), candidates[i], Type.CANDIDATE,
        this.height, false, undefined, this);
    candidateElem.render(this.interContainer_);
    var size = goog.style.getSize(candidateElem.getElement());
    var candidateWidth = size.width + CandidateView.PADDING_ * 2;
    // 1px is the width of the separator.
    w += candidateWidth + 1;

    if (w >= totalWidth) {
      if (i == 0) {
        // Make sure have one at least.
        candidateElem.setSize(totalWidth);
        ++i;
      } else {
        this.interContainer_.removeChild(candidateElem.getElement());
      }
      break;
    }
    candidateElem.setSize(candidateWidth);
  }
  this.candidateCount = i;
};


/**
 * Sets the widthInWeight which equals to a total line in the
 * keyset view and it is used for alignment of number row.
 *
 * @param {number} widthInWeight .
 * @param {number} backspaceWeight .
 */
CandidateView.prototype.setWidthInWeight = function(widthInWeight,
    backspaceWeight) {
  this.widthInWeight_ = widthInWeight;
  this.backspaceWeight_ = backspaceWeight;
};


/** @override */
CandidateView.prototype.resize = function(width, height) {
  goog.style.setSize(this.getElement(), width, height);

  var remainingWidth = width;
  if (this.backspaceWeight_ > 0) {
    var weightArray = [Math.round(this.widthInWeight_ - this.backspaceWeight_)];
    weightArray.push(this.backspaceWeight_);
    var values = util.splitValue(weightArray, width);
    this.iconWidth_ = values[values.length - 1];
  }
  for (var i = 0; i < this.iconButtons_.length; i++) {
    var button = this.iconButtons_[i];
    button.resize(this.iconWidth_, height);
  }
  // At most one icon in iconButtons can be visible and the space must be
  // reserved.
  remainingWidth = width - this.iconWidth_;


  for (var i = 0; i < this.fvkButtons_.length; i++) {
    var button = this.fvkButtons_[i];
    if (button.isVisible()) {
      // Uses square buttons
      button.resize(height, height);
      remainingWidth -= height;
    }
  }

  if (this.tryShowingToolbar) {
    if (this.hasEnoughSpaceForToolbar_()) {
      for (var i = 0; i < this.toolbarButtons_.length; i++) {
        var button = this.toolbarButtons_[i];
        button.resize(CandidateView.TOOLBAR_ICON_WIDTH_, height);
        // Button may be invisible previously due to not enough space.
        button.setVisible(true);
        remainingWidth -= CandidateView.TOOLBAR_ICON_WIDTH_;
      }
    } else {
      for (var i = 0; i < this.toolbarButtons_.length; i++) {
        this.toolbarButtons_[i].setVisible(false);
      }
    }
  }

  // Only show candidates which could fit into the remaining width.
  if (this.candidateCount > 0) {
    var w = 0;
    for (i = 0; i < this.candidateCount; i++) {
      if (w <= remainingWidth) {
        w += goog.style.getSize(this.interContainer_.children[i]).width;
      }
      goog.style.setElementShown(this.interContainer_.children[i],
          w <= remainingWidth);
    }
  }
  // Three candidates has width: auto.
  if (!goog.dom.classlist.contains(this.getElement(),
      i18n.input.chrome.inputview.Css.THREE_CANDIDATES)) {
    goog.style.setSize(this.interContainer_, remainingWidth, height);
  }

  goog.base(this, 'resize', width, height);

  if (this.candidateViewType_ == CandidateViewType.NUMBER_ROW) {
    this.showNumberRow();
  }
};


/**
 * Switches to the icon, or hide it.
 *
 * @param {number} type .
 * @param {boolean} visible The visibility of back button.
 */
CandidateView.prototype.switchToIcon = function(type, visible) {
  if (visible) {
    for (var i = 0; i < this.iconButtons_.length; i++) {
      if (type != IconType.VOICE) {
        this.iconButtons_[i].setVisible(i == type);
      } else {
        this.iconButtons_[i].setVisible(i == type &&
            this.needToShowVoiceIcon_());
      }
    }
  } else {
    this.iconButtons_[type].setVisible(false);
    // When some icon turn to invisible, need to show voice icon.
    if (type != IconType.VOICE && this.needToShowVoiceIcon_()) {
      this.iconButtons_[IconType.VOICE].setVisible(true);
    }
  }
};


/**
 * Changes the visibility of the floating virtual keyboard related buttons.
 *
 * @param {boolean} visible The drag button visibility.
 */
CandidateView.prototype.setFloatingVKButtonsVisible = function(visible) {
  for (var i = 0; i < this.fvkButtons_.length; i++) {
    this.fvkButtons_[i].setVisible(visible);
  }
};


/**
 * Changes the visibility of the toolbar and it's icons.
 *
 * @param {boolean} visible The target visibility.
 */
CandidateView.prototype.setToolbarVisible = function(visible) {
  this.tryShowingToolbar = visible;
  if (this.hasEnoughSpaceForToolbar_()) {
    for (var i = 0; i < this.toolbarButtons_.length; i++) {
      this.toolbarButtons_[i].setVisible(visible);
    }
  }
};


/**
 * Whether there is enough space to show toolbar in this view.
 *
 * @return {boolean}
 * @private
 */
CandidateView.prototype.hasEnoughSpaceForToolbar_ = function() {
  var toolbarSpace =
      CandidateView.TOOLBAR_ICON_WIDTH_ * this.toolbarButtons_.length;
  // Reserve space to display at least 3 candidates
  var candidatesSpace = CandidateView.WIDTH_FOR_THREE_CANDIDATES_ * 3;
  if (this.adapter_.isFloatingVirtualKeyboardEnabled()) {
    //TODO: large size floating virtual keyboard may still use the regular
    //width. Add an enum to distinguish small size from large and middle size
    //for floating virtual keyboard.
    candidatesSpace = CandidateView.SMALL_WIDTH_FOR_THREE_CANDIDATES_ * 3;
  }
  return toolbarSpace + candidatesSpace < this.width;
};


/**
 * Updates the candidate view by key set changing. Whether to show voice icon
 * or not.
 *
 * @param {string} keyset .
 * @param {boolean} isPasswordBox .
 * @param {boolean} isRTL .
 */
CandidateView.prototype.updateByKeyset = function(
    keyset, isPasswordBox, isRTL) {
  this.keyset_ = keyset;
  this.isPasswordBox_ = isPasswordBox;
  this.isRTL_ = isRTL;
  if (keyset == CandidateView.HANDWRITING_VIEW_CODE_ ||
      keyset == CandidateView.EMOJI_VIEW_CODE_) {
    // Handwriting and emoji keyset do not allow to show voice icon.
    this.switchToIcon(IconType.VOICE, false);
  } else {
    this.switchToIcon(IconType.VOICE, this.needToShowVoiceIcon_());
  }

  if (isPasswordBox && (keyset.indexOf('compact') != -1 &&
      keyset.indexOf('compact.symbol') == -1)) {
    this.showNumberRow();
  } else {
    this.hideNumberRow();
  }
  this.interContainer_.style.direction = isRTL ? 'rtl' : 'ltr';
};


/** @override */
CandidateView.prototype.disposeInternal = function() {
  goog.disposeAll(this.toolbarButtons_);
  goog.disposeAll(this.iconButtons_);

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


/**
 * Whether need to show voice icon on candidate view bar.
 *
 * @return {boolean}
 * @private
 */
CandidateView.prototype.needToShowVoiceIcon_ = function() {
  return this.adapter_.isVoiceInputEnabled &&
      this.adapter_.contextType != 'password' &&
      this.keyset_ != CandidateView.HANDWRITING_VIEW_CODE_ &&
      this.keyset_ != CandidateView.EMOJI_VIEW_CODE_ &&
      this.candidateViewType_ != CandidateViewType.TOOLTIP &&
      (!this.navigation_ || this.candidateCount == this.sumOfCandidates_);
};


/**
 * Sets the navigation value.
 *
 * @param {boolean} navigation .
 */
CandidateView.prototype.setNavigation = function(navigation) {
  this.navigation_ = navigation;
};
});  // goog.scope