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

goog.require('goog.dom.TagName');
goog.require('goog.dom.classlist');
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.EnterKey');
goog.require('i18n.input.chrome.inputview.elements.content.FunctionalKey');
goog.require('i18n.input.chrome.inputview.util');


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 FunctionalKey = i18n.input.chrome.inputview.elements.content.FunctionalKey;
var EnterKey = i18n.input.chrome.inputview.elements.content.EnterKey;
var util = i18n.input.chrome.inputview.util;



/**
 * The expanded candidate view.
 *
 * @param {!goog.events.EventTarget=} opt_eventTarget .
 * @constructor
 * @extends {i18n.input.chrome.inputview.elements.Element}
 */
i18n.input.chrome.inputview.elements.content.ExpandedCandidateView = function(
    opt_eventTarget) {
  goog.base(this, 'expandedCandidateView', ElementType.
      EXPANDED_CANDIDATE_VIEW, opt_eventTarget);

  /**
   * The four lines.
   *
   * @private {!Array.<!Element>}
   */
  this.lines_ = [];

  /**
   * The functional keys at the right.
   *
   * @private {!Object.<ElementType, FunctionalKey>}
   */
  this.keys_ = {};

  /**
   * Key: page index.
   * Value: candidate start index.
   *
   * @private {!Object.<number, number>}
   */
  this.pageIndexMap_ = {};
};
var ExpandedCandidateView = i18n.input.chrome.inputview.elements.content.
    ExpandedCandidateView;
goog.inherits(ExpandedCandidateView,
    i18n.input.chrome.inputview.elements.Element);


/**
 * The index of the key.
 *
 * @enum {number}
 */
ExpandedCandidateView.KeyIndex = {
  BACKSPACE: 0,
  ENTER: 1,
  PAGE_UP: 2,
  PAGE_DOWN: 3
};


/**
 * The state of the expanded candidate view.
 *
 * @enum {number}
 */
ExpandedCandidateView.State = {
  NONE: 0,
  COMPLETION_CORRECTION: 1,
  PREDICTION: 2
};


/**
 * The state of the expanded candidate view.
 *
 * @type {ExpandedCandidateView.State}
 */
ExpandedCandidateView.prototype.state = ExpandedCandidateView.State.NONE;


/**
 * The current page index.
 *
 * @private {number}
 */
ExpandedCandidateView.prototype.pageIndex_ = 0;


/** @private {number} */
ExpandedCandidateView.prototype.candidateStartIndex_ = 0;


/** @private {!Array.<!Object>} */
ExpandedCandidateView.prototype.candidates_;


/**
 * The padding between candidates.
 *
 * @private {number}
 */
ExpandedCandidateView.RIGHT_KEY_WIDTH_ = 120;


/**
 * How many cells divided in one line.
 *
 * @type {number}
 * @private
 */
ExpandedCandidateView.CELLS_PER_LINE_ = 10;


/** @private {number} */
ExpandedCandidateView.LINES_ = 4;


/** @private {number} */
ExpandedCandidateView.prototype.widthPerCell_ = 0;


/** @private {number} */
ExpandedCandidateView.prototype.heightPerCell_ = 0;


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


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


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

  var dom = this.getDomHelper();
  var line = this.createCandidateLine_(true);
  this.createKey_(ElementType.BACKSPACE_KEY, Css.BACKSPACE_ICON);

  line = this.createCandidateLine_(false);
  this.createKey_(ElementType.ENTER_KEY, Css.ENTER_ICON);

  line = this.createCandidateLine_(false);
  this.createKey_(ElementType.CANDIDATES_PAGE_UP, Css.PAGE_UP_ICON);

  line = this.createCandidateLine_(false);
  this.createKey_(ElementType.CANDIDATES_PAGE_DOWN, Css.PAGE_DOWN_ICON);
};


/**
 * Creates a line for the candidates.
 *
 * @param {boolean} isTopLine .
 * @private
 */
ExpandedCandidateView.prototype.createCandidateLine_ = function(isTopLine) {
  var dom = this.getDomHelper();
  var line = dom.createDom(TagName.DIV, [Css.CANDIDATE_INTER_CONTAINER,
    Css.CANDIDATES_LINE]);
  if (isTopLine) {
    goog.dom.classlist.add(line, Css.CANDIDATES_TOP_LINE);
  }
  dom.appendChild(this.getElement(), line);
  this.lines_.push(line);
};


/**
 * Creates the right functional key.
 *
 * @param {ElementType} type .
 * @param {string} iconCss .
 * @return {!i18n.input.chrome.inputview.elements.Element} key.
 * @private
 */
ExpandedCandidateView.prototype.createKey_ = function(type, iconCss) {
  var key;
  if (type == ElementType.ENTER_KEY) {
    key = new EnterKey('', iconCss, this);
  } else {
    key = new FunctionalKey('', type, '', iconCss, this);
  }
  key.render(this.getElement());
  goog.dom.classlist.add(key.getElement(), Css.INLINE_DIV);
  this.keys_[type] = key;
  return key;
};


/**
 * Pages up to show more candidates.
 */
ExpandedCandidateView.prototype.pageUp = function() {
  if (this.pageIndex_ > 0) {
    this.pageIndex_--;
    this.showCandidates(this.candidates_, this.pageIndexMap_[this.pageIndex_]);
  }
};


/**
 * Pages down to the previous candidate page.
 */
ExpandedCandidateView.prototype.pageDown = function() {
  if (this.candidates_.length > this.candidateStartIndex_) {
    this.pageIndex_++;
    this.showCandidates(this.candidates_, this.candidateStartIndex_);
  }
};


/**
 * Closes this view.
 */
ExpandedCandidateView.prototype.close = function() {
  this.candidates_ = [];
  this.pageIndex_ = 0;
  this.pageIndexMap_ = {};
  this.candidateStartIndex_ = 0;
  this.setVisible(false);
};


/**
 * Shows the candidates in expanded view.
 *
 * @param {!Array.<!Object>} candidates .
 * @param {number} start .
 */
ExpandedCandidateView.prototype.showCandidates = function(candidates, start) {
  this.setVisible(true);
  var dom = this.getDomHelper();
  for (var i = 0; i < this.lines_.length; i++) {
    dom.removeChildren(this.lines_[i]);
  }

  this.pageIndexMap_[this.pageIndex_] = start;
  this.candidates_ = candidates;
  var lineIndex = 0;
  var line = this.lines_[lineIndex];
  var cellsLeftInLine = ExpandedCandidateView.CELLS_PER_LINE_;
  var previousCandidate = null;
  var previousCandidateWidth = 0;
  var i;
  for (i = start; i < candidates.length; i++) {
    var candidate = candidates[i];
    var candidateElem = new Candidate(String(i), candidate, Type.CANDIDATE,
        this.heightPerCell_, false, undefined, this);
    candidateElem.render(line);
    var size = goog.style.getSize(candidateElem.getElement());
    var cellsOfCandidate = Math.ceil(size.width / this.widthPerCell_);
    if (cellsLeftInLine < cellsOfCandidate && previousCandidate) {
      // If there is not enough cells, just put this candidate to a new line
      // and give the rest cells to the last candidate.
      line.removeChild(candidateElem.getElement());
      // Will start new lines and set previous element as null,
      // then won't hit this code in next loop.
      i--;
      previousCandidate.setSize(
          cellsLeftInLine * this.widthPerCell_ + previousCandidateWidth,
          this.heightPerCell_);
      cellsLeftInLine = 0;
    } else if ((cellsLeftInLine < cellsOfCandidate && !previousCandidate) ||
        cellsLeftInLine == cellsOfCandidate) {
      // If there is not enough space and not any candidate is inserted in this
      // line, or after inert this candidate, there is 0 space left, then just
      // set the size of the current candidate.
      candidateElem.setSize(cellsLeftInLine * this.widthPerCell_,
          this.heightPerCell_);
      cellsLeftInLine = 0;
    } else {
      cellsLeftInLine -= cellsOfCandidate;
      candidateElem.setSize(cellsOfCandidate * this.widthPerCell_,
          this.heightPerCell_);
    }


    if (cellsLeftInLine == 0) {
      // Changes to new line if there is no space left.
      lineIndex++;
      if (lineIndex == ExpandedCandidateView.LINES_) {
        break;
      }
      cellsLeftInLine = ExpandedCandidateView.CELLS_PER_LINE_;
      line = this.lines_[lineIndex];
      previousCandidateWidth = 0;
      previousCandidate = null;
    } else {
      previousCandidateWidth = size.width;
      previousCandidate = candidateElem;
    }
  }
  this.candidateStartIndex_ = i;
  var pageDownKey = this.keys_[ElementType.CANDIDATES_PAGE_DOWN].getElement();
  var pageUpKey = this.keys_[ElementType.CANDIDATES_PAGE_UP].getElement();
  if (i >= candidates.length) {
    goog.dom.classlist.add(pageDownKey, Css.PAGE_NAVI_INACTIVE);
  } else {
    goog.dom.classlist.remove(pageDownKey, Css.PAGE_NAVI_INACTIVE);
  }

  if (this.pageIndex_ > 0) {
    goog.dom.classlist.remove(pageUpKey, Css.PAGE_NAVI_INACTIVE);
  } else {
    goog.dom.classlist.add(pageUpKey, Css.PAGE_NAVI_INACTIVE);
  }
};


/**
 * 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 .
 */
ExpandedCandidateView.prototype.setWidthInWeight = function(widthInWeight,
    backspaceWeight) {
  this.widthInWeight_ = widthInWeight;
  this.backspaceWeight_ = backspaceWeight;
};


/** @override */
ExpandedCandidateView.prototype.resize = function(width, height) {
  goog.base(this, 'resize', width, height);
  goog.style.setSize(this.getElement(), width, height);
  var lastKeyWidth = ExpandedCandidateView.RIGHT_KEY_WIDTH_;
  if (this.backspaceWeight_ > 0) {
    var weightArray = [Math.round(this.widthInWeight_ - this.backspaceWeight_)];
    weightArray.push(this.backspaceWeight_);
    var values = util.splitValue(weightArray, width);
    lastKeyWidth = values[values.length - 1];
  }

  var candidatesWidth = Math.floor(width - lastKeyWidth);
  this.widthPerCell_ = Math.floor(candidatesWidth /
      ExpandedCandidateView.CELLS_PER_LINE_);
  this.heightPerCell_ = Math.floor(height / ExpandedCandidateView.LINES_);
  for (var i = 0; i < this.lines_.length; i++) {
    var line = this.lines_[i];
    goog.style.setSize(line, candidatesWidth, this.heightPerCell_);
  }
  for (var type in this.keys_) {
    type = /** @type {ElementType} */ (Number(type));
    var key = this.keys_[type];
    key.resize(lastKeyWidth, this.heightPerCell_);
  }
};

});  // goog.scope