// Copyright (c) 2012 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. // TODO(kochi): Generalize the notification as a component and put it // in js/cr/ui/notification.js . cr.define('options', function() { /** @const */ var OptionsPage = options.OptionsPage; /** @const */ var LanguageList = options.LanguageList; /** * Spell check dictionary download status. * @type {Enum} */ /** @const*/ var DOWNLOAD_STATUS = { IN_PROGRESS: 1, FAILED: 2 }; /** * The preference is a boolean that enables/disables spell checking. * @type {string} * @const */ var ENABLE_SPELL_CHECK_PREF = 'browser.enable_spellchecking'; /** * The preference is a CSV string that describes preload engines * (i.e. active input methods). * @type {string} * @const */ var PRELOAD_ENGINES_PREF = 'settings.language.preload_engines'; /** * The preference that lists the extension IMEs that are enabled in the * language menu. * @type {string} * @const */ var ENABLED_EXTENSION_IME_PREF = 'settings.language.enabled_extension_imes'; /** * The preference that lists the languages which are not translated. * @type {string} * @const */ var TRANSLATE_BLOCKED_LANGUAGES_PREF = 'translate_blocked_languages'; /** * The preference key that is a string that describes the spell check * dictionary language, like "en-US". * @type {string} * @const */ var SPELL_CHECK_DICTIONARY_PREF = 'spellcheck.dictionary'; /** * The preference that indicates if the Translate feature is enabled. * @type {string} * @const */ var ENABLE_TRANSLATE = 'translate.enabled'; ///////////////////////////////////////////////////////////////////////////// // LanguageOptions class: /** * Encapsulated handling of ChromeOS language options page. * @constructor */ function LanguageOptions(model) { OptionsPage.call(this, 'languages', loadTimeData.getString('languagePageTabTitle'), 'languagePage'); } cr.addSingletonGetter(LanguageOptions); // Inherit LanguageOptions from OptionsPage. LanguageOptions.prototype = { __proto__: OptionsPage.prototype, /* For recording the prospective language (the next locale after relaunch). * @type {?string} * @private */ prospectiveUiLanguageCode_: null, /* * Map from language code to spell check dictionary download status for that * language. * @type {Array} * @private */ spellcheckDictionaryDownloadStatus_: [], /** * Number of times a spell check dictionary download failed. * @type {int} * @private */ spellcheckDictionaryDownloadFailures_: 0, /** * The list of preload engines, like ['mozc', 'pinyin']. * @type {Array} * @private */ preloadEngines_: [], /** * The list of extension IMEs that are enabled out of the language menu. * @type {Array} * @private */ enabledExtensionImes_: [], /** * The list of the languages which is not translated. * @type {Array} * @private */ translateBlockedLanguages_: [], /** * The list of the languages supported by Translate server * @type {Array} * @private */ translateSupportedLanguages_: [], /** * The preference is a string that describes the spell check dictionary * language, like "en-US". * @type {string} * @private */ spellCheckDictionary_: '', /** * The map of language code to input method IDs, like: * {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...} * @type {Object} * @private */ languageCodeToInputMethodIdsMap_: {}, /** * The value that indicates if Translate feature is enabled or not. * @type {boolean} * @private */ enableTranslate_: false, /** * Initializes LanguageOptions page. * Calls base class implementation to start preference initialization. */ initializePage: function() { OptionsPage.prototype.initializePage.call(this); var languageOptionsList = $('language-options-list'); LanguageList.decorate(languageOptionsList); languageOptionsList.addEventListener('change', this.handleLanguageOptionsListChange_.bind(this)); languageOptionsList.addEventListener('save', this.handleLanguageOptionsListSave_.bind(this)); this.prospectiveUiLanguageCode_ = loadTimeData.getString('prospectiveUiLanguageCode'); this.addEventListener('visibleChange', this.handleVisibleChange_.bind(this)); if (cr.isChromeOS) { this.initializeInputMethodList_(); this.initializeLanguageCodeToInputMethodIdsMap_(); } var checkbox = $('offer-to-translate-in-this-language'); checkbox.addEventListener('click', this.handleOfferToTranslateCheckboxClick_.bind(this)); Preferences.getInstance().addEventListener( TRANSLATE_BLOCKED_LANGUAGES_PREF, this.handleTranslateBlockedLanguagesPrefChange_.bind(this)); Preferences.getInstance().addEventListener(SPELL_CHECK_DICTIONARY_PREF, this.handleSpellCheckDictionaryPrefChange_.bind(this)); Preferences.getInstance().addEventListener(ENABLE_TRANSLATE, this.handleEnableTranslatePrefChange_.bind(this)); this.translateSupportedLanguages_ = loadTimeData.getValue('translateSupportedLanguages'); // Set up add button. $('language-options-add-button').onclick = function(e) { // Add the language without showing the overlay if it's specified in // the URL hash (ex. lang_add=ja). Used for automated testing. var match = document.location.hash.match(/\blang_add=([\w-]+)/); if (match) { var addLanguageCode = match[1]; $('language-options-list').addLanguage(addLanguageCode); this.addBlockedLanguage_(addLanguageCode); } else { OptionsPage.navigateToPage('addLanguage'); } }.bind(this); if (!cr.isMac) { // Set up the button for editing custom spelling dictionary. $('edit-dictionary-button').onclick = function(e) { OptionsPage.navigateToPage('editDictionary'); }; $('dictionary-download-retry-button').onclick = function(e) { chrome.send('retryDictionaryDownload'); }; } // Listen to add language dialog ok button. $('add-language-overlay-ok-button').addEventListener( 'click', this.handleAddLanguageOkButtonClick_.bind(this)); if (!cr.isChromeOS) { // Show experimental features if enabled. if (loadTimeData.getBoolean('enableSpellingAutoCorrect')) $('auto-spell-correction-option').hidden = false; // Handle spell check enable/disable. if (!cr.isMac) { Preferences.getInstance().addEventListener( ENABLE_SPELL_CHECK_PREF, this.updateEnableSpellCheck_.bind(this)); } } // Handle clicks on "Use this language for spell checking" button. if (!cr.isMac) { var spellCheckLanguageButton = getRequiredElement( 'language-options-spell-check-language-button'); spellCheckLanguageButton.addEventListener( 'click', this.handleSpellCheckLanguageButtonClick_.bind(this)); } if (cr.isChromeOS) { $('language-options-ui-restart-button').onclick = function() { chrome.send('uiLanguageRestart'); }; } $('language-confirm').onclick = OptionsPage.closeOverlay.bind(OptionsPage); }, /** * Initializes the input method list. */ initializeInputMethodList_: function() { var inputMethodList = $('language-options-input-method-list'); var inputMethodPrototype = $('language-options-input-method-template'); // Add all input methods, but make all of them invisible here. We'll // change the visibility in handleLanguageOptionsListChange_() based // on the selected language. Note that we only have less than 100 // input methods, so creating DOM nodes at once here should be ok. this.appendInputMethodElement_(loadTimeData.getValue('inputMethodList')); this.appendInputMethodElement_(loadTimeData.getValue('extensionImeList')); this.appendComponentExtensionIme_( loadTimeData.getValue('componentExtensionImeList')); // Listen to pref change once the input method list is initialized. Preferences.getInstance().addEventListener( PRELOAD_ENGINES_PREF, this.handlePreloadEnginesPrefChange_.bind(this)); Preferences.getInstance().addEventListener( ENABLED_EXTENSION_IME_PREF, this.handleEnabledExtensionsPrefChange_.bind(this)); }, /** * Appends input method lists based on component extension ime list. * @param {!Array} componentExtensionImeList A list of input method * descriptors. * @private */ appendComponentExtensionIme_: function(componentExtensionImeList) { this.appendInputMethodElement_(componentExtensionImeList); for (var i = 0; i < componentExtensionImeList.length; i++) { var inputMethod = componentExtensionImeList[i]; for (var languageCode in inputMethod.languageCodeSet) { if (languageCode in this.languageCodeToInputMethodIdsMap_) { this.languageCodeToInputMethodIdsMap_[languageCode].push( inputMethod.id); } else { this.languageCodeToInputMethodIdsMap_[languageCode] = [inputMethod.id]; } } } }, /** * Appends input methods into input method list. * @param {!Array} inputMethods A list of input method descriptors. * @private */ appendInputMethodElement_: function(inputMethods) { var inputMethodList = $('language-options-input-method-list'); var inputMethodTemplate = $('language-options-input-method-template'); for (var i = 0; i < inputMethods.length; i++) { var inputMethod = inputMethods[i]; var element = inputMethodTemplate.cloneNode(true); element.id = ''; element.languageCodeSet = inputMethod.languageCodeSet; var input = element.querySelector('input'); input.inputMethodId = inputMethod.id; var span = element.querySelector('span'); span.textContent = inputMethod.displayName; if (inputMethod.optionsPage) { var button = document.createElement('button'); button.textContent = loadTimeData.getString('configure'); button.inputMethodId = inputMethod.id; button.onclick = function(inputMethodId, e) { chrome.send('inputMethodOptionsOpen', [inputMethodId]); }.bind(this, inputMethod.id); element.appendChild(button); } // Listen to user clicks. input.addEventListener('click', this.handleCheckboxClick_.bind(this)); inputMethodList.appendChild(element); } }, /** * Adds a language to the preference 'translate_blocked_languages'. If * |langCode| is already added, nothing happens. |langCode| is converted * to a Translate language synonym before added. * @param {string} langCode A language code like 'en' * @private */ addBlockedLanguage_: function(langCode) { langCode = this.convertLangCodeForTranslation_(langCode); if (this.translateBlockedLanguages_.indexOf(langCode) == -1) { this.translateBlockedLanguages_.push(langCode); Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF, this.translateBlockedLanguages_, true); } }, /** * Removes a language from the preference 'translate_blocked_languages'. * If |langCode| doesn't exist in the preference, nothing happens. * |langCode| is converted to a Translate language synonym before removed. * @param {string} langCode A language code like 'en' * @private */ removeBlockedLanguage_: function(langCode) { langCode = this.convertLangCodeForTranslation_(langCode); if (this.translateBlockedLanguages_.indexOf(langCode) != -1) { this.translateBlockedLanguages_ = this.translateBlockedLanguages_.filter( function(langCodeNotTranslated) { return langCodeNotTranslated != langCode; }); Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF, this.translateBlockedLanguages_, true); } }, /** * Handles OptionsPage's visible property change event. * @param {Event} e Property change event. * @private */ handleVisibleChange_: function(e) { if (this.visible) { $('language-options-list').redraw(); chrome.send('languageOptionsOpen'); } }, /** * Handles languageOptionsList's change event. * @param {Event} e Change event. * @private */ handleLanguageOptionsListChange_: function(e) { var languageOptionsList = $('language-options-list'); var languageCode = languageOptionsList.getSelectedLanguageCode(); // If there's no selection, just return. if (!languageCode) return; // Select the language if it's specified in the URL hash (ex. lang=ja). // Used for automated testing. var match = document.location.hash.match(/\blang=([\w-]+)/); if (match) { var specifiedLanguageCode = match[1]; if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) { languageCode = specifiedLanguageCode; } } this.updateOfferToTranslateCheckbox_(languageCode); if (cr.isWindows || cr.isChromeOS) this.updateUiLanguageButton_(languageCode); this.updateSelectedLanguageName_(languageCode); if (!cr.isMac) this.updateSpellCheckLanguageButton_(languageCode); if (cr.isChromeOS) this.updateInputMethodList_(languageCode); this.updateLanguageListInAddLanguageOverlay_(); }, /** * Happens when a user changes back to the language they're currently using. */ currentLocaleWasReselected: function() { this.updateUiLanguageButton_( loadTimeData.getString('currentUiLanguageCode')); }, /** * Handles languageOptionsList's save event. * @param {Event} e Save event. * @private */ handleLanguageOptionsListSave_: function(e) { if (cr.isChromeOS) { // Sort the preload engines per the saved languages before save. this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_); this.savePreloadEnginesPref_(); } }, /** * Sorts preloadEngines_ by languageOptionsList's order. * @param {Array} preloadEngines List of preload engines. * @return {Array} Returns sorted preloadEngines. * @private */ sortPreloadEngines_: function(preloadEngines) { // For instance, suppose we have two languages and associated input // methods: // // - Korean: hangul // - Chinese: pinyin // // The preloadEngines preference should look like "hangul,pinyin". // If the user reverse the order, the preference should be reorderd // to "pinyin,hangul". var languageOptionsList = $('language-options-list'); var languageCodes = languageOptionsList.getLanguageCodes(); // Convert the list into a dictonary for simpler lookup. var preloadEngineSet = {}; for (var i = 0; i < preloadEngines.length; i++) { preloadEngineSet[preloadEngines[i]] = true; } // Create the new preload engine list per the language codes. var newPreloadEngines = []; for (var i = 0; i < languageCodes.length; i++) { var languageCode = languageCodes[i]; var inputMethodIds = this.languageCodeToInputMethodIdsMap_[ languageCode]; if (!inputMethodIds) continue; // Check if we have active input methods associated with the language. for (var j = 0; j < inputMethodIds.length; j++) { var inputMethodId = inputMethodIds[j]; if (inputMethodId in preloadEngineSet) { // If we have, add it to the new engine list. newPreloadEngines.push(inputMethodId); // And delete it from the set. This is necessary as one input // method can be associated with more than one language thus // we should avoid having duplicates in the new list. delete preloadEngineSet[inputMethodId]; } } } return newPreloadEngines; }, /** * Initializes the map of language code to input method IDs. * @private */ initializeLanguageCodeToInputMethodIdsMap_: function() { var inputMethodList = loadTimeData.getValue('inputMethodList'); for (var i = 0; i < inputMethodList.length; i++) { var inputMethod = inputMethodList[i]; for (var languageCode in inputMethod.languageCodeSet) { if (languageCode in this.languageCodeToInputMethodIdsMap_) { this.languageCodeToInputMethodIdsMap_[languageCode].push( inputMethod.id); } else { this.languageCodeToInputMethodIdsMap_[languageCode] = [inputMethod.id]; } } } }, /** * Updates the currently selected language name. * @param {string} languageCode Language code (ex. "fr"). * @private */ updateSelectedLanguageName_: function(languageCode) { var languageInfo = LanguageList.getLanguageInfoFromLanguageCode( languageCode); var languageDisplayName = languageInfo.displayName; var languageNativeDisplayName = languageInfo.nativeDisplayName; var textDirection = languageInfo.textDirection; // If the native name is different, add it. if (languageDisplayName != languageNativeDisplayName) { languageDisplayName += ' - ' + languageNativeDisplayName; } // Update the currently selected language name. var languageName = $('language-options-language-name'); languageName.textContent = languageDisplayName; languageName.dir = textDirection; }, /** * Updates the UI language button. * @param {string} languageCode Language code (ex. "fr"). * @private */ updateUiLanguageButton_: function(languageCode) { var uiLanguageButton = $('language-options-ui-language-button'); var uiLanguageMessage = $('language-options-ui-language-message'); var uiLanguageNotification = $('language-options-ui-notification-bar'); // Remove the event listener and add it back if useful. uiLanguageButton.onclick = null; // Unhide the language button every time, as it could've been previously // hidden by a language change. uiLanguageButton.hidden = false; if (languageCode == this.prospectiveUiLanguageCode_) { uiLanguageMessage.textContent = loadTimeData.getString('isDisplayedInThisLanguage'); showMutuallyExclusiveNodes( [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1); } else if (languageCode in loadTimeData.getValue('uiLanguageCodeSet')) { if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) { // In the guest mode for ChromeOS, changing UI language does not make // sense because it does not take effect after browser restart. uiLanguageButton.hidden = true; uiLanguageMessage.hidden = true; } else { uiLanguageButton.textContent = loadTimeData.getString('displayInThisLanguage'); showMutuallyExclusiveNodes( [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 0); uiLanguageButton.onclick = function(e) { chrome.send('uiLanguageChange', [languageCode]); }; } } else { uiLanguageMessage.textContent = loadTimeData.getString('cannotBeDisplayedInThisLanguage'); showMutuallyExclusiveNodes( [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1); } }, /** * Updates the spell check language button. * @param {string} languageCode Language code (ex. "fr"). * @private */ updateSpellCheckLanguageButton_: function(languageCode) { var spellCheckLanguageSection = $('language-options-spellcheck'); var spellCheckLanguageButton = $('language-options-spell-check-language-button'); var spellCheckLanguageMessage = $('language-options-spell-check-language-message'); var dictionaryDownloadInProgress = $('language-options-dictionary-downloading-message'); var dictionaryDownloadFailed = $('language-options-dictionary-download-failed-message'); var dictionaryDownloadFailHelp = $('language-options-dictionary-download-fail-help-message'); spellCheckLanguageSection.hidden = false; spellCheckLanguageMessage.hidden = true; spellCheckLanguageButton.hidden = true; dictionaryDownloadInProgress.hidden = true; dictionaryDownloadFailed.hidden = true; dictionaryDownloadFailHelp.hidden = true; if (languageCode == this.spellCheckDictionary_) { if (!(languageCode in this.spellcheckDictionaryDownloadStatus_)) { spellCheckLanguageMessage.textContent = loadTimeData.getString('isUsedForSpellChecking'); showMutuallyExclusiveNodes( [spellCheckLanguageButton, spellCheckLanguageMessage], 1); } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] == DOWNLOAD_STATUS.IN_PROGRESS) { dictionaryDownloadInProgress.hidden = false; } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] == DOWNLOAD_STATUS.FAILED) { spellCheckLanguageSection.hidden = true; dictionaryDownloadFailed.hidden = false; if (this.spellcheckDictionaryDownloadFailures_ > 1) dictionaryDownloadFailHelp.hidden = false; } } else if (languageCode in loadTimeData.getValue('spellCheckLanguageCodeSet')) { spellCheckLanguageButton.textContent = loadTimeData.getString('useThisForSpellChecking'); showMutuallyExclusiveNodes( [spellCheckLanguageButton, spellCheckLanguageMessage], 0); spellCheckLanguageButton.languageCode = languageCode; } else if (!languageCode) { spellCheckLanguageButton.hidden = true; spellCheckLanguageMessage.hidden = true; } else { spellCheckLanguageMessage.textContent = loadTimeData.getString('cannotBeUsedForSpellChecking'); showMutuallyExclusiveNodes( [spellCheckLanguageButton, spellCheckLanguageMessage], 1); } }, /** * Updates the checkbox for stopping translation. * @param {string} languageCode Language code (ex. "fr"). * @private */ updateOfferToTranslateCheckbox_: function(languageCode) { var div = $('language-options-offer-to-translate'); // Translation server supports Chinese (Transitional) and Chinese // (Simplified) but not 'general' Chinese. To avoid ambiguity, we don't // show this preference when general Chinese is selected. if (languageCode != 'zh') { div.hidden = false; } else { div.hidden = true; return; } var offerToTranslate = div.querySelector('div'); var cannotTranslate = $('cannot-translate-in-this-language'); var nodes = [offerToTranslate, cannotTranslate]; var convertedLangCode = this.convertLangCodeForTranslation_(languageCode); if (this.translateSupportedLanguages_.indexOf(convertedLangCode) != -1) { showMutuallyExclusiveNodes(nodes, 0); } else { showMutuallyExclusiveNodes(nodes, 1); return; } var checkbox = $('offer-to-translate-in-this-language'); if (!this.enableTranslate_) { checkbox.disabled = true; checkbox.checked = false; return; } // If the language corresponds to the default target language (in most // cases, the user's locale language), "Offer to translate" checkbox // should be always unchecked. var defaultTargetLanguage = loadTimeData.getString('defaultTargetLanguage'); if (convertedLangCode == defaultTargetLanguage) { checkbox.disabled = true; checkbox.checked = false; return; } checkbox.disabled = false; var blockedLanguages = this.translateBlockedLanguages_; var checked = blockedLanguages.indexOf(convertedLangCode) == -1; checkbox.checked = checked; }, /** * Updates the input method list. * @param {string} languageCode Language code (ex. "fr"). * @private */ updateInputMethodList_: function(languageCode) { // Give one of the checkboxes or buttons focus, if it's specified in the // URL hash (ex. focus=mozc). Used for automated testing. var focusInputMethodId = -1; var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/); if (match) { focusInputMethodId = match[1]; } // Change the visibility of the input method list. Input methods that // matches |languageCode| will become visible. var inputMethodList = $('language-options-input-method-list'); var methods = inputMethodList.querySelectorAll('.input-method'); for (var i = 0; i < methods.length; i++) { var method = methods[i]; if (languageCode in method.languageCodeSet) { method.hidden = false; var input = method.querySelector('input'); // Give it focus if the ID matches. if (input.inputMethodId == focusInputMethodId) { input.focus(); } } else { method.hidden = true; } } $('language-options-input-method-none').hidden = (languageCode in this.languageCodeToInputMethodIdsMap_); if (focusInputMethodId == 'add') { $('language-options-add-button').focus(); } }, /** * Updates the language list in the add language overlay. * @param {string} languageCode Language code (ex. "fr"). * @private */ updateLanguageListInAddLanguageOverlay_: function(languageCode) { // Change the visibility of the language list in the add language // overlay. Languages that are already active will become invisible, // so that users don't add the same language twice. var languageOptionsList = $('language-options-list'); var languageCodes = languageOptionsList.getLanguageCodes(); var languageCodeSet = {}; for (var i = 0; i < languageCodes.length; i++) { languageCodeSet[languageCodes[i]] = true; } var addLanguageList = $('add-language-overlay-language-list'); var options = addLanguageList.querySelectorAll('option'); assert(options.length > 0); var selectedFirstItem = false; for (var i = 0; i < options.length; i++) { var option = options[i]; option.hidden = option.value in languageCodeSet; if (!option.hidden && !selectedFirstItem) { // Select first visible item, otherwise previously selected hidden // item will be selected by default at the next time. option.selected = true; selectedFirstItem = true; } } }, /** * Handles preloadEnginesPref change. * @param {Event} e Change event. * @private */ handlePreloadEnginesPrefChange_: function(e) { var value = e.value.value; this.preloadEngines_ = this.filterBadPreloadEngines_(value.split(',')); this.updateCheckboxesFromPreloadEngines_(); $('language-options-list').updateDeletable(); }, /** * Handles enabledExtensionImePref change. * @param {Event} e Change event. * @private */ handleEnabledExtensionsPrefChange_: function(e) { var value = e.value.value; this.enabledExtensionImes_ = value.split(','); this.updateCheckboxesFromEnabledExtensions_(); }, /** * Handles offer-to-translate checkbox's click event. * @param {Event} e Click event. * @private */ handleOfferToTranslateCheckboxClick_: function(e) { var checkbox = e.target; var checked = checkbox.checked; var languageOptionsList = $('language-options-list'); var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode(); if (checked) this.removeBlockedLanguage_(selectedLanguageCode); else this.addBlockedLanguage_(selectedLanguageCode); }, /** * Handles input method checkbox's click event. * @param {Event} e Click event. * @private */ handleCheckboxClick_: function(e) { var checkbox = e.target; if (checkbox.inputMethodId.match(/^_ext_ime_/)) { this.updateEnabledExtensionsFromCheckboxes_(); this.saveEnabledExtensionPref_(); return; } if (this.preloadEngines_.length == 1 && !checkbox.checked) { // Don't allow disabling the last input method. this.showNotification_( loadTimeData.getString('pleaseAddAnotherInputMethod'), loadTimeData.getString('okButton')); checkbox.checked = true; return; } if (checkbox.checked) { chrome.send('inputMethodEnable', [checkbox.inputMethodId]); } else { chrome.send('inputMethodDisable', [checkbox.inputMethodId]); } this.updatePreloadEnginesFromCheckboxes_(); this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_); this.savePreloadEnginesPref_(); }, handleAddLanguageOkButtonClick_: function() { var languagesSelect = $('add-language-overlay-language-list'); var selectedIndex = languagesSelect.selectedIndex; if (selectedIndex >= 0) { var selection = languagesSelect.options[selectedIndex]; var langCode = String(selection.value); $('language-options-list').addLanguage(langCode); this.addBlockedLanguage_(langCode); OptionsPage.closeOverlay(); } }, /** * Checks if languageCode is deletable or not. * @param {string} languageCode the languageCode to check for deletability. */ languageIsDeletable: function(languageCode) { // Don't allow removing the language if it's a UI language. if (languageCode == this.prospectiveUiLanguageCode_) return false; return (!cr.isChromeOS || this.canDeleteLanguage_(languageCode)); }, /** * Handles browse.enable_spellchecking change. * @param {Event} e Change event. * @private */ updateEnableSpellCheck_: function() { var value = !$('enable-spell-check').checked; $('language-options-spell-check-language-button').disabled = value; if (!cr.IsMac) $('edit-dictionary-button').hidden = value; }, /** * Handles translateBlockedLanguagesPref change. * @param {Event} e Change event. * @private */ handleTranslateBlockedLanguagesPrefChange_: function(e) { this.translateBlockedLanguages_ = e.value.value; this.updateOfferToTranslateCheckbox_( $('language-options-list').getSelectedLanguageCode()); }, /** * Handles spellCheckDictionaryPref change. * @param {Event} e Change event. * @private */ handleSpellCheckDictionaryPrefChange_: function(e) { var languageCode = e.value.value; this.spellCheckDictionary_ = languageCode; if (!cr.isMac) { this.updateSpellCheckLanguageButton_( $('language-options-list').getSelectedLanguageCode()); } }, /** * Handles translate.enabled change. * @param {Event} e Change event. * @private */ handleEnableTranslatePrefChange_: function(e) { var enabled = e.value.value; this.enableTranslate_ = enabled; this.updateOfferToTranslateCheckbox_( $('language-options-list').getSelectedLanguageCode()); }, /** * Handles spellCheckLanguageButton click. * @param {Event} e Click event. * @private */ handleSpellCheckLanguageButtonClick_: function(e) { var languageCode = e.target.languageCode; // Save the preference. Preferences.setStringPref(SPELL_CHECK_DICTIONARY_PREF, languageCode, true); chrome.send('spellCheckLanguageChange', [languageCode]); }, /** * Checks whether it's possible to remove the language specified by * languageCode and returns true if possible. This function returns false * if the removal causes the number of preload engines to be zero. * * @param {string} languageCode Language code (ex. "fr"). * @return {boolean} Returns true on success. * @private */ canDeleteLanguage_: function(languageCode) { // First create the set of engines to be removed from input methods // associated with the language code. var enginesToBeRemovedSet = {}; var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode]; // If this language doesn't have any input methods, it can be deleted. if (!inputMethodIds) return true; for (var i = 0; i < inputMethodIds.length; i++) { enginesToBeRemovedSet[inputMethodIds[i]] = true; } // Then eliminate engines that are also used for other active languages. // For instance, if "xkb:us::eng" is used for both English and Filipino. var languageCodes = $('language-options-list').getLanguageCodes(); for (var i = 0; i < languageCodes.length; i++) { // Skip the target language code. if (languageCodes[i] == languageCode) { continue; } // Check if input methods used in this language are included in // enginesToBeRemovedSet. If so, eliminate these from the set, so // we don't remove this time. var inputMethodIdsForAnotherLanguage = this.languageCodeToInputMethodIdsMap_[languageCodes[i]]; if (!inputMethodIdsForAnotherLanguage) continue; for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) { var inputMethodId = inputMethodIdsForAnotherLanguage[j]; if (inputMethodId in enginesToBeRemovedSet) { delete enginesToBeRemovedSet[inputMethodId]; } } } // Update the preload engine list with the to-be-removed set. var newPreloadEngines = []; for (var i = 0; i < this.preloadEngines_.length; i++) { if (!(this.preloadEngines_[i] in enginesToBeRemovedSet)) { newPreloadEngines.push(this.preloadEngines_[i]); } } // Don't allow this operation if it causes the number of preload // engines to be zero. return (newPreloadEngines.length > 0); }, /** * Saves the enabled extension preference. * @private */ saveEnabledExtensionPref_: function() { Preferences.setStringPref(ENABLED_EXTENSION_IME_PREF, this.enabledExtensionImes_.join(','), true); }, /** * Updates the checkboxes in the input method list from the enabled * extensions preference. * @private */ updateCheckboxesFromEnabledExtensions_: function() { // Convert the list into a dictonary for simpler lookup. var dictionary = {}; for (var i = 0; i < this.enabledExtensionImes_.length; i++) dictionary[this.enabledExtensionImes_[i]] = true; var inputMethodList = $('language-options-input-method-list'); var checkboxes = inputMethodList.querySelectorAll('input'); for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].inputMethodId.match(/^_ext_ime_/)) checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary); } var configureButtons = inputMethodList.querySelectorAll('button'); for (var i = 0; i < configureButtons.length; i++) { if (configureButtons[i].inputMethodId.match(/^_ext_ime_/)) { configureButtons[i].hidden = !(configureButtons[i].inputMethodId in dictionary); } } }, /** * Updates the enabled extensions preference from the checkboxes in the * input method list. * @private */ updateEnabledExtensionsFromCheckboxes_: function() { this.enabledExtensionImes_ = []; var inputMethodList = $('language-options-input-method-list'); var checkboxes = inputMethodList.querySelectorAll('input'); for (var i = 0; i < checkboxes.length; i++) { if (checkboxes[i].inputMethodId.match(/^_ext_ime_/)) { if (checkboxes[i].checked) this.enabledExtensionImes_.push(checkboxes[i].inputMethodId); } } }, /** * Saves the preload engines preference. * @private */ savePreloadEnginesPref_: function() { Preferences.setStringPref(PRELOAD_ENGINES_PREF, this.preloadEngines_.join(','), true); }, /** * Updates the checkboxes in the input method list from the preload * engines preference. * @private */ updateCheckboxesFromPreloadEngines_: function() { // Convert the list into a dictonary for simpler lookup. var dictionary = {}; for (var i = 0; i < this.preloadEngines_.length; i++) { dictionary[this.preloadEngines_[i]] = true; } var inputMethodList = $('language-options-input-method-list'); var checkboxes = inputMethodList.querySelectorAll('input'); for (var i = 0; i < checkboxes.length; i++) { if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/)) checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary); } var configureButtons = inputMethodList.querySelectorAll('button'); for (var i = 0; i < configureButtons.length; i++) { if (!configureButtons[i].inputMethodId.match(/^_ext_ime_/)) { configureButtons[i].hidden = !(configureButtons[i].inputMethodId in dictionary); } } }, /** * Updates the preload engines preference from the checkboxes in the * input method list. * @private */ updatePreloadEnginesFromCheckboxes_: function() { this.preloadEngines_ = []; var inputMethodList = $('language-options-input-method-list'); var checkboxes = inputMethodList.querySelectorAll('input'); for (var i = 0; i < checkboxes.length; i++) { if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/)) { if (checkboxes[i].checked) this.preloadEngines_.push(checkboxes[i].inputMethodId); } } var languageOptionsList = $('language-options-list'); languageOptionsList.updateDeletable(); }, /** * Filters bad preload engines in case bad preload engines are * stored in the preference. Removes duplicates as well. * @param {Array} preloadEngines List of preload engines. * @private */ filterBadPreloadEngines_: function(preloadEngines) { // Convert the list into a dictonary for simpler lookup. var dictionary = {}; var list = loadTimeData.getValue('inputMethodList'); for (var i = 0; i < list.length; i++) { dictionary[list[i].id] = true; } var enabledPreloadEngines = []; var seen = {}; for (var i = 0; i < preloadEngines.length; i++) { // Check if the preload engine is present in the // dictionary, and not duplicate. Otherwise, skip it. // Component Extension IME should be handled same as preloadEngines and // "_comp_" is the special prefix of its ID. if ((preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) || /^_comp_/.test(preloadEngines[i])) { enabledPreloadEngines.push(preloadEngines[i]); seen[preloadEngines[i]] = true; } } return enabledPreloadEngines; }, // TODO(kochi): This is an adapted copy from new_tab.js. // If this will go as final UI, refactor this to share the component with // new new tab page. /** * Shows notification * @private */ notificationTimeout_: null, showNotification_: function(text, actionText, opt_delay) { var notificationElement = $('notification'); var actionLink = notificationElement.querySelector('.link-color'); var delay = opt_delay || 10000; function show() { window.clearTimeout(this.notificationTimeout_); notificationElement.classList.add('show'); document.body.classList.add('notification-shown'); } function hide() { window.clearTimeout(this.notificationTimeout_); notificationElement.classList.remove('show'); document.body.classList.remove('notification-shown'); // Prevent tabbing to the hidden link. actionLink.tabIndex = -1; // Setting tabIndex to -1 only prevents future tabbing to it. If, // however, the user switches window or a tab and then moves back to // this tab the element may gain focus. We therefore make sure that we // blur the element so that the element focus is not restored when // coming back to this window. actionLink.blur(); } function delayedHide() { this.notificationTimeout_ = window.setTimeout(hide, delay); } notificationElement.firstElementChild.textContent = text; actionLink.textContent = actionText; actionLink.onclick = hide; actionLink.onkeydown = function(e) { if (e.keyIdentifier == 'Enter') { hide(); } }; notificationElement.onmouseover = show; notificationElement.onmouseout = delayedHide; actionLink.onfocus = show; actionLink.onblur = delayedHide; // Enable tabbing to the link now that it is shown. actionLink.tabIndex = 0; show(); delayedHide(); }, onDictionaryDownloadBegin_: function(languageCode) { this.spellcheckDictionaryDownloadStatus_[languageCode] = DOWNLOAD_STATUS.IN_PROGRESS; if (!cr.isMac && languageCode == $('language-options-list').getSelectedLanguageCode()) { this.updateSpellCheckLanguageButton_(languageCode); } }, onDictionaryDownloadSuccess_: function(languageCode) { delete this.spellcheckDictionaryDownloadStatus_[languageCode]; this.spellcheckDictionaryDownloadFailures_ = 0; if (!cr.isMac && languageCode == $('language-options-list').getSelectedLanguageCode()) { this.updateSpellCheckLanguageButton_(languageCode); } }, onDictionaryDownloadFailure_: function(languageCode) { this.spellcheckDictionaryDownloadStatus_[languageCode] = DOWNLOAD_STATUS.FAILED; this.spellcheckDictionaryDownloadFailures_++; if (!cr.isMac && languageCode == $('language-options-list').getSelectedLanguageCode()) { this.updateSpellCheckLanguageButton_(languageCode); } }, /* * Converts the language code for Translation. There are some differences * between the language set for Translation and that for Accept-Language. * @param {string} languageCode The language code like 'fr'. * @return {string} The converted language code. * @private */ convertLangCodeForTranslation_: function(languageCode) { var tokens = languageCode.split('-'); var main = tokens[0]; // See also: chrome/renderer/translate/translate_helper.cc. var synonyms = { 'nb': 'no', 'he': 'iw', 'jv': 'jw', 'fil': 'tl', }; if (main in synonyms) { return synonyms[main]; } else if (main == 'zh') { // In Translation, general Chinese is not used, and the sub code is // necessary as a language code for Translate server. return languageCode; } return main; }, }; /** * Shows the node at |index| in |nodes|, hides all others. * @param {Array<HTMLElement>} nodes The nodes to be shown or hidden. * @param {number} index The index of |nodes| to show. */ function showMutuallyExclusiveNodes(nodes, index) { assert(index >= 0 && index < nodes.length); for (var i = 0; i < nodes.length; ++i) { assert(nodes[i] instanceof HTMLElement); // TODO(dbeam): Ignore null? nodes[i].hidden = i != index; } } /** * Chrome callback for when the UI language preference is saved. * @param {string} languageCode The newly selected language to use. */ LanguageOptions.uiLanguageSaved = function(languageCode) { this.prospectiveUiLanguageCode_ = languageCode; // If the user is no longer on the same language code, ignore. if ($('language-options-list').getSelectedLanguageCode() != languageCode) return; // Special case for when a user changes to a different language, and changes // back to the same language without having restarted Chrome or logged // in/out of ChromeOS. if (languageCode == loadTimeData.getString('currentUiLanguageCode')) { LanguageOptions.getInstance().currentLocaleWasReselected(); return; } // Otherwise, show a notification telling the user that their changes will // only take effect after restart. showMutuallyExclusiveNodes([$('language-options-ui-language-button'), $('language-options-ui-notification-bar')], 1); }; LanguageOptions.onDictionaryDownloadBegin = function(languageCode) { LanguageOptions.getInstance().onDictionaryDownloadBegin_(languageCode); }; LanguageOptions.onDictionaryDownloadSuccess = function(languageCode) { LanguageOptions.getInstance().onDictionaryDownloadSuccess_(languageCode); }; LanguageOptions.onDictionaryDownloadFailure = function(languageCode) { LanguageOptions.getInstance().onDictionaryDownloadFailure_(languageCode); }; LanguageOptions.onComponentManagerInitialized = function(componentImes) { LanguageOptions.getInstance().appendComponentExtensionIme_(componentImes); }; // Export return { LanguageOptions: LanguageOptions }; });