// 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