diff options
Diffstat (limited to 'chromium/chrome/browser/resources/options/language_options.js')
-rw-r--r-- | chromium/chrome/browser/resources/options/language_options.js | 1304 |
1 files changed, 1304 insertions, 0 deletions
diff --git a/chromium/chrome/browser/resources/options/language_options.js b/chromium/chrome/browser/resources/options/language_options.js new file mode 100644 index 00000000000..3ebf7d2665b --- /dev/null +++ b/chromium/chrome/browser/resources/options/language_options.js @@ -0,0 +1,1304 @@ +// 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 + }; +}); |