blob: 165063447f6f470583a54ccb07bc2d802ed92887 [file] [log] [blame]
// Copyright 2014 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.
/**
* @fileoverview
* Class managing the host's available keyboard layouts, allowing the user to
* select one that matches the local layout, or auto-selecting based on the
* current locale.
*/
'use strict';
/** @suppress {duplicate} */
var remoting = remoting || {};
/**
* @param {remoting.ContextMenuAdapter} adapter
* @constructor
*/
remoting.KeyboardLayoutsMenu = function(adapter) {
/** @private {remoting.ContextMenuAdapter} */
this.adapter_ = adapter;
/** @private {remoting.SubmenuManager} */
this.submenuManager_ = new remoting.SubmenuManager(
adapter,
chrome.i18n.getMessage(/*i18n-content*/'KEYBOARD_LAYOUTS_SUBMENU_TITLE'),
true);
/** @private {string} */
this.currentLayout_ = '';
/** @private {function(string, string)} */
this.sendExtensionMessage_ = base.doNothing;
adapter.addListener(this.onContextMenu_.bind(this));
};
/**
* @param {Array<string>} layouts The keyboard layouts available on the host,
* for example en-US, de-DE
* @param {string} currentLayout The layout currently active on the host.
*/
remoting.KeyboardLayoutsMenu.prototype.setLayouts =
function(layouts, currentLayout) {
this.submenuManager_.removeAll();
this.currentLayout_ = '';
for (var i = 0; i < layouts.length; ++i) {
this.submenuManager_.add(this.makeMenuId_(layouts[i]), layouts[i]);
}
// Pick a suitable default layout.
this.getBestLayout_(layouts, currentLayout,
this.setLayout_.bind(this, false));
};
/** @param {function(string, string)} callback */
remoting.KeyboardLayoutsMenu.prototype.setExtensionMessageSender =
function(callback) {
this.sendExtensionMessage_ = callback;
};
/**
* Notify the host that a new keyboard layout has been selected.
*
* @param {boolean} saveToLocalStorage If true, save the specified layout to
* local storage.
* @param {string} layout The new keyboard layout.
* @private
*/
remoting.KeyboardLayoutsMenu.prototype.setLayout_ =
function(saveToLocalStorage, layout) {
if (this.currentLayout_ != '') {
this.adapter_.updateCheckState(
this.makeMenuId_(this.currentLayout_), false);
}
this.adapter_.updateCheckState(this.makeMenuId_(layout), true);
this.currentLayout_ = layout;
console.log("Setting the keyboard layout to '" + layout + "'");
this.sendExtensionMessage_('setKeyboardLayout',
JSON.stringify({layout: layout}));
if (saveToLocalStorage) {
var params = {};
params[remoting.KeyboardLayoutsMenu.KEY_] = layout;
chrome.storage.local.set(params);
}
};
/**
* Choose the best keyboard from the alternatives, based on the following
* algorithm:
* - Search local storage by for a preferred keyboard layout for the app;
* if it is found, prefer it over the current locale, falling back on the
* latter only if no match is found.
* - If the candidate layout matches one of the supported layouts, use it.
* - Otherwise, if the language portion of the candidate matches that of
* any of the supported layouts, use the first such layout (e.g, en-AU
* will match either en-US or en-GB, whichever appears first).
* - Otherwise, use the host's current layout.
*
* @param {Array<string>} layouts
* @param {string} currentHostLayout
* @param {function(string):void} onDone
* @private
*/
remoting.KeyboardLayoutsMenu.prototype.getBestLayout_ =
function(layouts, currentHostLayout, onDone) {
/**
* Extract the language id from a string that is either "language" (e.g.
* "de") or "language-region" (e.g. "en-US").
*
* @param {string} layout
* @return {string}
*/
var getLanguage = function(layout) {
var languageAndRegion = layout.split('-');
switch (languageAndRegion.length) {
case 1:
case 2:
return languageAndRegion[0];
default:
return '';
}
};
/** @param {Object<string>} storage */
var chooseLayout = function(storage) {
var configuredLayout = storage[remoting.KeyboardLayoutsMenu.KEY_];
var tryLayouts = [ chrome.i18n.getUILanguage() ];
if (configuredLayout && typeof(configuredLayout) == 'string') {
tryLayouts.unshift(configuredLayout);
}
for (var i = 0; i < tryLayouts.length; ++i) {
if (layouts.indexOf(tryLayouts[i]) != -1) {
onDone(tryLayouts[i]);
return;
}
var language = getLanguage(tryLayouts[i]);
if (language) {
for (var j = 0; j < layouts.length; ++j) {
if (language == getLanguage(layouts[j])) {
onDone(layouts[j]);
return;
}
}
}
}
// Neither the stored layout nor UI locale was suitable.
onDone(currentHostLayout);
};
chrome.storage.local.get(remoting.KeyboardLayoutsMenu.KEY_, chooseLayout);
};
/**
* Create a menu id from the given keyboard layout.
*
* @param {string} layout Keyboard layout
* @return {string}
* @private
*/
remoting.KeyboardLayoutsMenu.prototype.makeMenuId_ = function(layout) {
return 'layout@' + layout;
};
/**
* Handle a click on the application's context menu.
*
* @param {OnClickData=} info
* @private
*/
remoting.KeyboardLayoutsMenu.prototype.onContextMenu_ = function(info) {
var menuItemId = info.menuItemId.toString();
var components = menuItemId.split('@');
if (components.length == 2 &&
this.makeMenuId_(components[1]) === menuItemId) {
this.setLayout_(true, components[1]);
}
};
/**
* @type {string}
* @private
*/
remoting.KeyboardLayoutsMenu.KEY_ = 'preferred-keyboard-layout';